"""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