feat: webhook event firing on issue creation (background task)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException, status
|
from fastapi import FastAPI, Depends, HTTPException, status, BackgroundTasks
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List
|
from typing import List
|
||||||
@@ -11,6 +11,7 @@ from pydantic import BaseModel
|
|||||||
from app.core.config import get_db, settings
|
from app.core.config import get_db, settings
|
||||||
from app.models import models
|
from app.models import models
|
||||||
from app.schemas import schemas
|
from app.schemas import schemas
|
||||||
|
from app.services.webhook import fire_webhooks_sync
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="HarborForge API",
|
title="HarborForge API",
|
||||||
@@ -99,11 +100,13 @@ async def get_me(current_user: models.User = Depends(get_current_user)):
|
|||||||
# ============ Issues API ============
|
# ============ Issues API ============
|
||||||
|
|
||||||
@app.post("/issues", response_model=schemas.IssueResponse, status_code=status.HTTP_201_CREATED)
|
@app.post("/issues", response_model=schemas.IssueResponse, status_code=status.HTTP_201_CREATED)
|
||||||
def create_issue(issue: schemas.IssueCreate, db: Session = Depends(get_db)):
|
def create_issue(issue: schemas.IssueCreate, bg: BackgroundTasks, db: Session = Depends(get_db)):
|
||||||
db_issue = models.Issue(**issue.model_dump())
|
db_issue = models.Issue(**issue.model_dump())
|
||||||
db.add(db_issue)
|
db.add(db_issue)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_issue)
|
db.refresh(db_issue)
|
||||||
|
event = "resolution.created" if db_issue.issue_type == "resolution" else "issue.created"
|
||||||
|
bg.add_task(fire_webhooks_sync, event, {"issue_id": db_issue.id, "title": db_issue.title, "type": db_issue.issue_type, "status": db_issue.status}, db_issue.project_id, db)
|
||||||
return db_issue
|
return db_issue
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
56
app/services/webhook.py
Normal file
56
app/services/webhook.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import json
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.models.webhook import Webhook, WebhookLog
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def fire_webhooks_sync(event: str, payload: dict, project_id: int, db: Session):
|
||||||
|
"""Find matching webhooks and send payloads (sync version)."""
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
webhooks = db.query(Webhook).filter(Webhook.is_active == True).all()
|
||||||
|
|
||||||
|
matched = []
|
||||||
|
for wh in webhooks:
|
||||||
|
events = [e.strip() for e in wh.events.split(",")]
|
||||||
|
if event not in events:
|
||||||
|
continue
|
||||||
|
if wh.project_id is not None and wh.project_id != project_id:
|
||||||
|
continue
|
||||||
|
matched.append(wh)
|
||||||
|
|
||||||
|
if not matched:
|
||||||
|
return
|
||||||
|
|
||||||
|
payload_json = json.dumps(payload, default=str)
|
||||||
|
|
||||||
|
for wh in matched:
|
||||||
|
log = WebhookLog(
|
||||||
|
webhook_id=wh.id,
|
||||||
|
event=event,
|
||||||
|
payload=payload_json,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if wh.secret:
|
||||||
|
sig = hmac.new(
|
||||||
|
wh.secret.encode(), payload_json.encode(), hashlib.sha256
|
||||||
|
).hexdigest()
|
||||||
|
headers["X-Webhook-Signature"] = sig
|
||||||
|
|
||||||
|
with httpx.Client(timeout=10.0) as client:
|
||||||
|
resp = client.post(wh.url, content=payload_json, headers=headers)
|
||||||
|
log.response_status = resp.status_code
|
||||||
|
log.response_body = resp.text[:1000]
|
||||||
|
log.success = 200 <= resp.status_code < 300
|
||||||
|
except Exception as e:
|
||||||
|
log.response_body = str(e)[:1000]
|
||||||
|
log.success = False
|
||||||
|
logger.warning(f"Webhook delivery failed for {wh.url}: {e}")
|
||||||
|
|
||||||
|
db.add(log)
|
||||||
|
db.commit()
|
||||||
@@ -10,3 +10,4 @@ bcrypt==4.0.1
|
|||||||
python-multipart==0.0.6
|
python-multipart==0.0.6
|
||||||
alembic==1.13.1
|
alembic==1.13.1
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
|
httpx==0.27.0
|
||||||
|
|||||||
Reference in New Issue
Block a user