Make webhooks async

This commit is contained in:
Minecon724 2025-04-03 18:04:27 +02:00
commit 94e9d5ea75
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
2 changed files with 74 additions and 54 deletions

View file

@ -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')

View file

@ -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)