diff --git a/.env.example b/.env.example index 1047377..9ac4f3a 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,5 @@ DB_HOST= DB_PORT= SECRET_KEY= CONNECTION_NAME= +RETRY_LOGS_BATCH_LIMIT= +MAX_RETRY_ATTEMPTS_FOR_LOGS= diff --git a/api/mixins.py b/api/mixins.py index 6e0f76f..36cb42c 100644 --- a/api/mixins.py +++ b/api/mixins.py @@ -1,10 +1,8 @@ from __future__ import absolute_import - +from datetime import datetime from api import db class TimestampMixin(object): - created_on = db.Column(db.DateTime, server_default=db.func.now()) - updated_on = db.Column( - db.DateTime, server_onupdate=db.func.now(), server_default=db.func.now() - ) + created_on = db.Column(db.DateTime, default=datetime.now) + updated_on = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now) diff --git a/api/models/ivr_callback_transaction_log.py b/api/models/ivr_callback_transaction_log.py index 4dc966e..e5e56f9 100644 --- a/api/models/ivr_callback_transaction_log.py +++ b/api/models/ivr_callback_transaction_log.py @@ -7,3 +7,4 @@ class IvrCallbackTransactionLog(TimestampMixin, db.Model): id = db.Column(db.Integer, primary_key=True) payload = db.Column(db.Text) processed = db.Column(db.Boolean, nullable=False) + attempts = db.Column(db.Integer, nullable=False, default="0") diff --git a/api/services/call_log_event_service.py b/api/services/call_log_event_service.py index 044bd2d..f9f3a3a 100644 --- a/api/services/call_log_event_service.py +++ b/api/services/call_log_event_service.py @@ -1,6 +1,7 @@ # This file is treated as service layer from flask import request from api import models, db +from datetime import datetime class CallLogEventService: @@ -20,6 +21,9 @@ def create_call_log_event(self, data): pick_time=data["pick_time"], end_time=data["end_time"], duration=data["duration"], + created_on=data["log_created_on"] + if data.get("log_created_on", None) + else datetime.now(), ) db.session.add(call_log_event) db.session.commit() diff --git a/api/services/handle_event_service.py b/api/services/handle_event_service.py index 93ba7d9..3b6c7f9 100644 --- a/api/services/handle_event_service.py +++ b/api/services/handle_event_service.py @@ -1,21 +1,20 @@ import requests from flask import request -from api import models, db +from api import models, db, app from . import registration_service as registration from . import call_log_event_service as call_log_event class HandleEventService: - def handle_event_service(self, request): - + def handle_event_service(self, form_data): call_sid_exist = db.session.query( - db.exists().where(models.CallLogEvent.call_sid == request.form["CallSid"]) + db.exists().where(models.CallLogEvent.call_sid == form_data["CallSid"]) ).scalar() if call_sid_exist: return - url_decoded_system_phone = requests.utils.unquote(request.form["To"]) + url_decoded_system_phone = requests.utils.unquote(form_data["To"]) system_phone_exists = db.session.query( db.exists().where(models.SystemPhone.phone == url_decoded_system_phone) @@ -26,13 +25,13 @@ def handle_event_service(self, request): try: data = {} - data["call_sid"] = request.form["CallSid"] - data["account_sid"] = request.form["AccountSid"] - data["from_number"] = request.form["From"] + data["call_sid"] = form_data["CallSid"] + data["account_sid"] = form_data["AccountSid"] + data["from_number"] = form_data["From"] data["to_number"] = url_decoded_system_phone - data["call_status"] = request.form["CallStatus"] - data["direction"] = request.form["Direction"] - data["parent_call_sid"] = request.form["ParentCallSid"] + data["call_status"] = form_data["CallStatus"] + data["direction"] = form_data["Direction"] + data["parent_call_sid"] = form_data["ParentCallSid"] data["telco_code"] = None data["telco_status"] = None @@ -41,18 +40,21 @@ def handle_event_service(self, request): data["end_time"] = None data["duration"] = None + if form_data.get("log_created_on", None): + data["log_created_on"] = form_data["log_created_on"] + if data["call_status"] != "Missed": - data["telco_code"] = request.form["TelcoCode"] - data["telco_status"] = request.form["TelcoStatus"] - data["dial_time"] = request.form["DialTime"] - data["end_time"] = request.form["EndTime"] - data["duration"] = request.form["Duration"] + data["telco_code"] = form_data["TelcoCode"] + data["telco_status"] = form_data["TelcoStatus"] + data["dial_time"] = form_data["DialTime"] + data["end_time"] = form_data["EndTime"] + data["duration"] = form_data["Duration"] """PickTime is available only when the call is answered. The telco code for answered calls is 16. """ - if request.form["TelcoCode"] == "16": - data["pick_time"] = request.form["PickTime"] + if form_data["TelcoCode"] == app.config["TELCO_CODE_ANSWERED"]: + data["pick_time"] = form_data["PickTime"] call_log_event.CallLogEventService.create_call_log_event(self, data) if data["call_status"] == "Missed": diff --git a/api/services/registration_service.py b/api/services/registration_service.py index eb00bde..bbca83e 100644 --- a/api/services/registration_service.py +++ b/api/services/registration_service.py @@ -1,12 +1,11 @@ # This file is treated as service layer -from flask import request from api import models, db +from datetime import datetime class RegistrationService: # Create a registartion def create_registration(self, data): - system_phone_data = models.SystemPhone.query.filter_by( phone=data["to_number"] ).first() @@ -31,6 +30,9 @@ def create_registration(self, data): partner_id=partner.partner_id, state=system_phone_data.state, has_dropped_missedcall=True, + created_on=data["log_created_on"] + if data.get("log_created_on", None) + else datetime.now(), ) db.session.add(registration) db.session.commit() diff --git a/api/services/transaction_log_service.py b/api/services/transaction_log_service.py index d742568..947f897 100644 --- a/api/services/transaction_log_service.py +++ b/api/services/transaction_log_service.py @@ -1,5 +1,5 @@ # This file is treated as service layer -from api import models, db +from api import models, db, app import json @@ -16,3 +16,19 @@ def mark_ivr_transaction_log_as_processed(self, ivr_transaction_log): ivr_transaction_log.processed = True db.session.add(ivr_transaction_log) db.session.commit() + + def get_failed_ivr_transaction_log(self): + failed_ivr_transaction_logs = ( + models.IvrCallbackTransactionLog.query.filter( + models.IvrCallbackTransactionLog.processed == False + ) + .filter( + models.IvrCallbackTransactionLog.attempts + < app.config["MAX_RETRY_ATTEMPTS_FOR_LOGS"] + ) + .order_by(models.IvrCallbackTransactionLog.id) + .limit(app.config["RETRY_LOGS_BATCH_LIMIT"]) + .all() + ) + + return failed_ivr_transaction_logs diff --git a/config.py b/config.py index a849820..9712eff 100644 --- a/config.py +++ b/config.py @@ -36,3 +36,7 @@ SQLALCHEMY_TRACK_MODIFICATIONS = True WTF_CSRF_ENABLED = True SECRET_KEY = os.environ.get("SECRET_KEY") + +RETRY_LOGS_BATCH_LIMIT = os.environ.get("RETRY_LOGS_BATCH_LIMIT", 1000) +TELCO_CODE_ANSWERED = 16 +MAX_RETRY_ATTEMPTS_FOR_LOGS = os.environ.get("MAX_RETRY_ATTEMPTS_FOR_LOGS", 3) diff --git a/main.py b/main.py index 0776622..47b2ab3 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,28 @@ # from pprint import pprint from api import models, db, services from flask import jsonify, request +import json def callback(request): try: if request.method == "POST": - formData = request.form + json_data = request.get_json() + form_data = request.form transaction_log_service = services.TransactionLogService() + + if json_data and json_data.get("type", None) == "retry_failed_log": + retry_failed_webhook(transaction_log_service) + return "Success" + ivr_transaction_log = ( - transaction_log_service.create_new_ivr_transaction_log(formData) + transaction_log_service.create_new_ivr_transaction_log(form_data) ) - service = services.HandleEventService() - service.handle_event_service(request) + processed = process_form_data(form_data) + + if not processed: + return jsonify(message="Something went wrong!"), 400 + transaction_log_service.mark_ivr_transaction_log_as_processed( ivr_transaction_log ) @@ -21,6 +31,28 @@ def callback(request): jsonify(message="Currently, the system do not accept a GET request"), 405, ) - except IndexError: + except: return jsonify(message="Something went wrong!"), 400 + return "Success" + + +def retry_failed_webhook(transaction_log_service): + failed_ivr_logs = transaction_log_service.get_failed_ivr_transaction_log() + + for log in failed_ivr_logs: + payload = json.loads(log.payload) + payload["log_created_on"] = log.created_on + log.processed = process_form_data(payload) + log.attempts += 1 + db.session.add(log) + db.session.commit() + + +def process_form_data(form_data): + try: + service = services.HandleEventService() + service.handle_event_service(form_data) + return True + except: + return False