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