PMFlow/backend/app/api/webhooks.py

116 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Webhook 接收 API。"""
import json
import re
from datetime import datetime, timezone
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from ..core.database import get_db
from ..models import Signal
from ..services.llm_service import llm_service
from ..services.integrations.integration_core.plane_service import plane_service
from ..utils.timezone import now_cn_aware
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
@router.post("/git")
async def handle_git_webhook(
payload: dict,
db: AsyncSession = Depends(get_db),
):
"""接收 Git Webhook,审计代码并更新状态"""
commits = payload.get("commits", [])
if not commits:
return {"status": "ignored", "reason": "No commits found"}
updated_tasks = []
for commit in commits:
message = commit.get("message", "")
code_diff = commit.get("patch") or str(commit.get("modified", []))
task_matches = re.findall(
r"#([A-Za-z0-9-]+-\d+|[A-Za-z0-9]+-\d+)", message
)
for task_id in task_matches:
result = await db.execute(
select(Signal).order_by(Signal.created_at.desc()).limit(20)
)
signals = result.scalars().all()
target_task = None
target_signal = None
for sig in signals:
if not sig.tasks_generated:
continue
try:
tasks = json.loads(sig.tasks_generated)
for t in tasks:
if t.get("plane_key") == task_id or t.get(
"plane_id"
) == task_id:
target_task = t
target_signal = sig
break
except Exception:
continue
if target_task:
break
task_name = (
target_task.get("task_name") if target_task else f"Task {task_id}"
)
# 尽量从目标信号推断租户,避免跨租户预算串账。
tenant_id = int(getattr(target_signal, "tenant_id", 1) or 1)
try:
audit_result = await llm_service.audit_code_change_async(
task_name,
message,
code_diff,
db=db,
tenant_id=tenant_id,
)
except RuntimeError:
updated_tasks.append(
{
"task_id": task_id,
"status": "Blocked",
"audit": {"passed": False, "reason": "预算超限,AI 审计被阻止"},
}
)
continue
if audit_result.get("passed"):
await plane_service.update_issue_status(task_id, status="Done")
if target_signal and target_task:
tasks = json.loads(target_signal.tasks_generated)
for t in tasks:
if t.get("plane_key") == task_id or t.get(
"plane_id"
) == task_id:
t["status"] = "Done"
t["audit_feedback"] = audit_result.get("reason")
t["completed_at"] = (
now_cn_aware().isoformat()
)
target_signal.tasks_generated = json.dumps(tasks)
await db.commit()
updated_tasks.append(
{"task_id": task_id, "status": "Done", "audit": audit_result}
)
else:
updated_tasks.append(
{
"task_id": task_id,
"status": "Failed",
"audit": audit_result,
}
)
return {"status": "processed", "updated_tasks": updated_tasks}