From 43ff0c5db934ae51be5c7d6c1b0a1e0c3e8f54ab Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Thu, 13 Aug 2026 00:46:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20V1=20-=20DAG=E7=BC=96=E6=8E=92/?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E7=B3=BB=E7=BB=9F/Agent=E5=BE=AA=E7=8E=AF/?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93RAG/Webhook=20+=20=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=AF=8C=E4=B8=8A=E4=B8=8B=E6=96=87=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/webhook_service.py | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 backend/app/services/webhook_service.py diff --git a/backend/app/services/webhook_service.py b/backend/app/services/webhook_service.py new file mode 100644 index 0000000..ca490fc --- /dev/null +++ b/backend/app/services/webhook_service.py @@ -0,0 +1,78 @@ +"""Webhook service: trigger external notifications.""" +import json +import hmac +import hashlib +import httpx +from sqlalchemy.orm import Session +from typing import List, Optional +from app.models.webhook import WebhookConfig + + +class WebhookService: + @staticmethod + def trigger(db: Session, tenant_id: int, event_type: str, payload: dict): + """Find matching webhooks and POST to their URLs.""" + hooks = db.query(WebhookConfig).filter( + WebhookConfig.tenant_id == tenant_id, + WebhookConfig.is_active == True, + ).all() + + for hook in hooks: + events = json.loads(hook.events or "[]") + if event_type not in events: + continue + try: + body = json.dumps({"event": event_type, "data": payload}, ensure_ascii=False) + headers = {"Content-Type": "application/json"} + if hook.secret: + sig = hmac.new( + hook.secret.encode(), body.encode(), hashlib.sha256 + ).hexdigest() + headers["X-Webhook-Signature"] = sig + + with httpx.Client(timeout=10) as client: + client.post(hook.url, content=body, headers=headers) + except Exception: + pass # webhook delivery is best-effort + + @staticmethod + def list_hooks(db, tenant_id): + return db.query(WebhookConfig).filter( + WebhookConfig.tenant_id == tenant_id + ).order_by(WebhookConfig.created_at.desc()).all() + + @staticmethod + def create_hook(db, tenant_id, req): + hook = WebhookConfig( + tenant_id=tenant_id, url=req.url, events=req.events, + secret=req.secret, project_id=req.project_id, + ) + db.add(hook) + db.commit() + db.refresh(hook) + return hook + + @staticmethod + def update_hook(db, tenant_id, hook_id, req): + hook = db.query(WebhookConfig).filter( + WebhookConfig.id == hook_id, WebhookConfig.tenant_id == tenant_id + ).first() + if not hook: + return None + for f in ["url", "events", "secret", "is_active"]: + if hasattr(req, f) and getattr(req, f) is not None: + setattr(hook, f, getattr(req, f)) + db.commit() + db.refresh(hook) + return hook + + @staticmethod + def delete_hook(db, tenant_id, hook_id): + hook = db.query(WebhookConfig).filter( + WebhookConfig.id == hook_id, WebhookConfig.tenant_id == tenant_id + ).first() + if not hook: + return False + db.delete(hook) + db.commit() + return True