diff --git a/src/anonchat/admin_routes.py b/src/anonchat/admin_routes.py index 71d145d..24ad5f7 100644 --- a/src/anonchat/admin_routes.py +++ b/src/anonchat/admin_routes.py @@ -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') diff --git a/src/anonchat/webhooks.py b/src/anonchat/webhooks.py index 07ce38b..7af5566 100644 --- a/src/anonchat/webhooks.py +++ b/src/anonchat/webhooks.py @@ -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) \ No newline at end of file