auto-sync: 2026-05-18 00:40:10
This commit is contained in:
@@ -31,9 +31,10 @@ def _q(project_id: str) -> Queries:
|
||||
@router.get("/tasks")
|
||||
async def list_tasks(project_id: str,
|
||||
status: Optional[str] = None,
|
||||
assignee: Optional[str] = None):
|
||||
assignee: Optional[str] = None,
|
||||
card_id: Optional[str] = None):
|
||||
bb = _bb(project_id)
|
||||
tasks = bb.list_tasks(status=status, assignee=assignee)
|
||||
tasks = bb.list_tasks(status=status, assignee=assignee, card_id=card_id)
|
||||
return {"tasks": [_task_to_dict(t) for t in tasks]}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
"""API 路由 — Mail Tab(v2.7)
|
||||
|
||||
Mail 是一个特殊的 Project (_mail),每个 Mail 是一个点对点的双节点 Task。
|
||||
显示形式为独立 Tab,以列表形式展示。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from src.blackboard.operations import Blackboard
|
||||
from src.blackboard.models import Task
|
||||
from src.blackboard.db import init_db
|
||||
from src.utils import get_data_root
|
||||
|
||||
router = APIRouter(prefix="/api/mail", tags=["mail"])
|
||||
|
||||
MAIL_PROJECT_ID = "_mail"
|
||||
|
||||
|
||||
def _db_path() -> Path:
|
||||
root = get_data_root()
|
||||
db = root / MAIL_PROJECT_ID / "blackboard.db"
|
||||
db.parent.mkdir(parents=True, exist_ok=True)
|
||||
init_db(db)
|
||||
return db
|
||||
|
||||
|
||||
def _bb() -> Blackboard:
|
||||
return Blackboard(_db_path())
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_mail(status: Optional[str] = None,
|
||||
_from: Optional[str] = None,
|
||||
to: Optional[str] = None,
|
||||
limit: int = Query(50, le=200)):
|
||||
"""Mail 列表"""
|
||||
bb = _bb()
|
||||
tasks = bb.list_tasks(status=status, assignee=to)
|
||||
|
||||
result = []
|
||||
for t in tasks:
|
||||
# 解析 Mail 元数据
|
||||
meta = json.loads(t.must_haves or "{}") if t.must_haves else {}
|
||||
result.append({
|
||||
"id": t.id,
|
||||
"title": t.title,
|
||||
"from": meta.get("from", t.assigned_by),
|
||||
"to": t.assignee,
|
||||
"status": t.status,
|
||||
"type": meta.get("type", "inform"),
|
||||
"is_read": meta.get("is_read", False),
|
||||
"created_at": t.created_at,
|
||||
"description": t.description,
|
||||
})
|
||||
|
||||
return {"mails": result, "total": len(result)}
|
||||
|
||||
|
||||
@router.get("/{mail_id}")
|
||||
async def get_mail(mail_id: str):
|
||||
"""Mail 详情"""
|
||||
bb = _bb()
|
||||
task = bb.get_task(mail_id)
|
||||
if not task:
|
||||
raise HTTPException(404, f"Mail not found: {mail_id}")
|
||||
|
||||
meta = json.loads(task.must_haves or "{}") if task.must_haves else {}
|
||||
return {
|
||||
"id": task.id,
|
||||
"title": task.title,
|
||||
"from": meta.get("from", task.assigned_by),
|
||||
"to": task.assignee,
|
||||
"status": task.status,
|
||||
"type": meta.get("type", "inform"),
|
||||
"is_read": meta.get("is_read", False),
|
||||
"created_at": task.created_at,
|
||||
"description": task.description,
|
||||
"outputs": [dict(o.__dict__) for o in bb.get_outputs(mail_id)],
|
||||
"comments": [dict(c.__dict__) for c in bb.get_comments(mail_id)],
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/{mail_id}")
|
||||
async def update_mail(mail_id: str, body: Dict[str, Any]):
|
||||
"""更新 Mail(标记已读/已执行)"""
|
||||
bb = _bb()
|
||||
task = bb.get_task(mail_id)
|
||||
if not task:
|
||||
raise HTTPException(404, f"Mail not found: {mail_id}")
|
||||
|
||||
# 更新 must_haves 里的 is_read
|
||||
meta = json.loads(task.must_haves or "{}") if task.must_haves else {}
|
||||
if "is_read" in body:
|
||||
meta["is_read"] = body["is_read"]
|
||||
|
||||
# 如果标记为已执行,更新状态
|
||||
if body.get("mark_executed"):
|
||||
meta["is_read"] = True
|
||||
if task.status not in ("done", "cancelled"):
|
||||
bb.update_task_status(mail_id, "done", agent="mail-api")
|
||||
|
||||
# 写回 must_haves
|
||||
conn = bb._conn()
|
||||
try:
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
conn.execute(
|
||||
"UPDATE tasks SET must_haves=? WHERE id=?",
|
||||
(json.dumps(meta), mail_id),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def send_mail(body: Dict[str, Any]):
|
||||
"""发送 Mail(创建 Task)"""
|
||||
bb = _bb()
|
||||
meta = {
|
||||
"from": body.get("from", "user"),
|
||||
"type": body.get("type", "inform"),
|
||||
"is_read": False,
|
||||
}
|
||||
task = Task(
|
||||
id=body["id"],
|
||||
title=body["title"],
|
||||
description=body.get("description", ""),
|
||||
assignee=body.get("to"),
|
||||
assigned_by=body.get("from", "user"),
|
||||
must_haves=json.dumps(meta),
|
||||
card_id="default",
|
||||
task_type="mail",
|
||||
)
|
||||
bb.create_task(task)
|
||||
return {"ok": True, "mail_id": task.id}
|
||||
Reference in New Issue
Block a user