auto-sync: 2026-05-18 00:40:10

This commit is contained in:
cfdaily
2026-05-18 00:40:10 +08:00
parent 20135f2e26
commit a96a8a4e56
2 changed files with 146 additions and 2 deletions
+3 -2
View File
@@ -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]}
+143
View File
@@ -0,0 +1,143 @@
"""API 路由 — Mail Tabv2.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}