Make webhooks async
This commit is contained in:
parent
cab6e5ec34
commit
94e9d5ea75
2 changed files with 74 additions and 54 deletions
|
@ -96,18 +96,18 @@ def admin_settings_webhook():
|
|||
settings.webhook_enabled = request.form.get('webhook_enabled') == 'on'
|
||||
settings.webhook_url = request.form.get('webhook_url', '')
|
||||
settings.webhook_secret = request.form.get('webhook_secret', '')
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Test webhook if enabled and URL is provided
|
||||
if settings.webhook_enabled and settings.webhook_url:
|
||||
try:
|
||||
inquiry_created('1234abcd1234abcd', 'This is a test message')
|
||||
inquiry_created('1234abcd1234abcd', 'This is a test message', is_async=False)
|
||||
flash('Webhook settings saved and test sent successfully')
|
||||
except WebhookError as e:
|
||||
flash(f'Webhook test failed: {str(e)}')
|
||||
else:
|
||||
flash('Webhook settings saved')
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('admin_settings'))
|
||||
|
||||
|
@ -125,7 +125,7 @@ def admin_settings():
|
|||
@admin_required
|
||||
def admin_settings_password():
|
||||
admin = Admin.query.filter_by(username=session['admin_username']).first()
|
||||
|
||||
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
|
|
@ -3,6 +3,8 @@ import urllib.error
|
|||
import hmac
|
||||
import hashlib
|
||||
import json
|
||||
import threading
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
from .models import Settings
|
||||
|
@ -23,71 +25,89 @@ class WebhookError(Exception):
|
|||
super().__init__(self.message)
|
||||
|
||||
|
||||
def _send_webhook(event_type, data):
|
||||
"""Send webhook if enabled with proper signature"""
|
||||
settings = Settings.query.first()
|
||||
if not settings or not settings.webhook_enabled or not settings.webhook_url:
|
||||
return False
|
||||
|
||||
payload = {
|
||||
"event_type": event_type,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"data": data
|
||||
}
|
||||
|
||||
# Convert payload to JSON
|
||||
payload_str = json.dumps(payload).encode('utf-8')
|
||||
|
||||
# Create request with headers
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if settings.webhook_secret:
|
||||
signature = hmac.new(
|
||||
settings.webhook_secret.encode(),
|
||||
payload_str,
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
headers['X-Webhook-Signature'] = signature
|
||||
|
||||
def _send_webhook(event_type, data, is_async=False):
|
||||
app = current_app._get_current_object()
|
||||
|
||||
if is_async:
|
||||
_send_webhook_async(event_type, data, app)
|
||||
else:
|
||||
_send_webhook_worker(event_type, data, app)
|
||||
|
||||
def _send_webhook_async(event_type, data, app):
|
||||
"""Queue webhook to be sent in a background thread"""
|
||||
thread = threading.Thread(target=_send_webhook_worker, args=(event_type, data, app))
|
||||
thread.daemon = True # Thread will exit when main thread exits
|
||||
thread.start()
|
||||
|
||||
|
||||
def _send_webhook_worker(event_type, data, app):
|
||||
"""Worker function that sends the webhook in a background thread"""
|
||||
try:
|
||||
# Create request object
|
||||
req = urllib.request.Request(
|
||||
settings.webhook_url,
|
||||
data=payload_str,
|
||||
headers=headers,
|
||||
method='POST'
|
||||
)
|
||||
|
||||
# Set timeout
|
||||
with urllib.request.urlopen(req, timeout=5) as response:
|
||||
if response.status != 200:
|
||||
raise WebhookError(f"Webhook error: {response.status}", status_code=response.status)
|
||||
with app.app_context():
|
||||
settings = Settings.query.first()
|
||||
if not settings or not settings.webhook_enabled or not settings.webhook_url:
|
||||
return False
|
||||
|
||||
payload = {
|
||||
"event_type": event_type,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"data": data
|
||||
}
|
||||
|
||||
# Convert payload to JSON
|
||||
payload_str = json.dumps(payload).encode('utf-8')
|
||||
|
||||
# Create request with headers
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if settings.webhook_secret:
|
||||
signature = hmac.new(
|
||||
settings.webhook_secret.encode(),
|
||||
payload_str,
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
headers['X-Webhook-Signature'] = signature
|
||||
|
||||
# Create request object
|
||||
req = urllib.request.Request(
|
||||
settings.webhook_url,
|
||||
data=payload_str,
|
||||
headers=headers,
|
||||
method='POST'
|
||||
)
|
||||
|
||||
# Set timeout
|
||||
with urllib.request.urlopen(req, timeout=5) as response:
|
||||
if response.status != 200:
|
||||
raise WebhookError("Status code not 200", status_code=response.status)
|
||||
except Exception as e:
|
||||
status_code = None
|
||||
if hasattr(e, 'status_code'):
|
||||
status_code = e.status_code
|
||||
raise WebhookError(f"Webhook error: {str(e)}", status_code=status_code, original_exception=e)
|
||||
|
||||
def inquiry_created(inquiry_id, message):
|
||||
app.logger.error(f"Webhook error: {e}")
|
||||
|
||||
if isinstance(e, WebhookError):
|
||||
raise e
|
||||
|
||||
raise WebhookError(f"Exception: {e}", original_exception=e)
|
||||
|
||||
def inquiry_created(inquiry_id, message, is_async=True):
|
||||
_send_webhook('inquiry_created', {
|
||||
'inquiry_id': inquiry_id,
|
||||
'message': message
|
||||
})
|
||||
}, is_async)
|
||||
|
||||
def inquiry_reopened(inquiry_id):
|
||||
def inquiry_reopened(inquiry_id, is_async=True):
|
||||
_send_webhook('inquiry_reopened', {
|
||||
'inquiry_id': inquiry_id
|
||||
})
|
||||
}, is_async)
|
||||
|
||||
|
||||
def inquiry_closed(inquiry_id):
|
||||
def inquiry_closed(inquiry_id, is_async=True):
|
||||
_send_webhook('inquiry_closed', {
|
||||
'inquiry_id': inquiry_id
|
||||
})
|
||||
}, is_async)
|
||||
|
||||
def inquiry_message(inquiry_id, message):
|
||||
def inquiry_message(inquiry_id, message, is_async=True):
|
||||
_send_webhook('inquiry_message', {
|
||||
'inquiry_id': inquiry_id,
|
||||
'message': message
|
||||
})
|
||||
}, is_async)
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue