116 lines
4.0 KiB
Python
116 lines
4.0 KiB
Python
"""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 ..database import get_db
|
||
from ..models import Signal
|
||
from ..services.llm import llm_service
|
||
from ..services.plane 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}
|