待办:项目待办模型 + /projects/{id}/todos 接口 + 控制台待办页
This commit is contained in:
1 parent
c10fda4e4a
commit
b3b3b35042
8 files changed
+962
-23
No files matched your search
+164
@@ -104,6 +104,11 @@ from cloud.wait_ops import report_handoff as _handoff_report
|
||||
from cloud.wait_ops import worktree_status as _wait_worktree_status
|
||||
from cloud.automations import ensure_automations_tables as _auto_ensure
|
||||
from cloud.automations import list_automations as _auto_list
|
||||
from cloud.todos import create_todo as _todo_create
|
||||
from cloud.todos import delete_todo as _todo_delete
|
||||
from cloud.todos import get_todo as _todo_get
|
||||
from cloud.todos import list_todos as _todo_list
|
||||
from cloud.todos import update_todo as _todo_update
|
||||
from cloud.automations import triage_inbox as _auto_triage
|
||||
from cloud.approvals import create_approval as _appr_create
|
||||
from cloud.approvals import decide_approval as _appr_decide
|
||||
@@ -3320,6 +3325,45 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
return
|
||||
self._triage_inbox(project_id)
|
||||
return
|
||||
# /projects/{id}/todos GET 列表 / POST 新建;单条
|
||||
# GET / PATCH / DELETE(项目 read / create_task)
|
||||
if len(parts) == 2 and parts[1] == "todos":
|
||||
if method == "GET":
|
||||
err = self._check_project(user_id, project_id, "read")
|
||||
if err:
|
||||
self._send_json(err[0], {"error": err[1]})
|
||||
return
|
||||
self._todos_list(project_id, query)
|
||||
elif method == "POST":
|
||||
err = self._check_project(
|
||||
user_id, project_id, "create_task")
|
||||
if err:
|
||||
self._send_json(err[0], {"error": err[1]})
|
||||
return
|
||||
self._todos_create(user_id, project_id)
|
||||
else:
|
||||
self._send_json(404, {"error": "not found"})
|
||||
return
|
||||
if len(parts) == 3 and parts[1] == "todos":
|
||||
if method == "GET":
|
||||
err = self._check_project(user_id, project_id, "read")
|
||||
if err:
|
||||
self._send_json(err[0], {"error": err[1]})
|
||||
return
|
||||
self._todos_get(project_id, parts[2])
|
||||
elif method in ("PATCH", "DELETE"):
|
||||
err = self._check_project(
|
||||
user_id, project_id, "create_task")
|
||||
if err:
|
||||
self._send_json(err[0], {"error": err[1]})
|
||||
return
|
||||
if method == "PATCH":
|
||||
self._todos_update(user_id, project_id, parts[2])
|
||||
else:
|
||||
self._todos_delete(user_id, project_id, parts[2])
|
||||
else:
|
||||
self._send_json(404, {"error": "not found"})
|
||||
return
|
||||
# /projects/{id}/skills GET(项目 read)
|
||||
if len(parts) == 2 and parts[1] == "skills":
|
||||
if method != "GET":
|
||||
@@ -9132,6 +9176,126 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
"due_count": sum(1 for a in autos if a["due"]),
|
||||
})
|
||||
|
||||
# -- 项目待办(见 cloud/todos.py)-------------------------
|
||||
def _todos_list(self, project_id, query):
|
||||
"""列出某项目待办,附全项目各状态计数;query 支持
|
||||
status 与 q(标题/描述子串)服务端过滤,计数与
|
||||
total 不受过滤影响。"""
|
||||
conn = self._db()
|
||||
try:
|
||||
try:
|
||||
todos = _todo_list(
|
||||
conn, project_id,
|
||||
status=query.get("status", [None])[0] or None,
|
||||
q=query.get("q", [None])[0] or None)
|
||||
# counts / total 是全项目口径,不受过滤影响
|
||||
#(与任务列表的 status_counts 同款语义)。
|
||||
all_todos = _todo_list(conn, project_id)
|
||||
except Exception as exc:
|
||||
self._send_json(502, {"error": str(exc)[:500]})
|
||||
return
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
counts = {}
|
||||
for t in all_todos:
|
||||
counts[t["status"]] = counts.get(t["status"], 0) + 1
|
||||
self._send_json(200, {
|
||||
"project_id": project_id, "todos": todos,
|
||||
"counts": counts, "total": len(all_todos)})
|
||||
|
||||
def _todos_create(self, user_id, project_id):
|
||||
"""新建待办:body title 必填;description / status /
|
||||
priority / task_id / due_at / sort_order 可选。"""
|
||||
body, err = self._read_json()
|
||||
if err:
|
||||
self._send_json(400, {"error": err})
|
||||
return
|
||||
conn = self._db()
|
||||
try:
|
||||
try:
|
||||
todo = _todo_create(
|
||||
conn, project_id, user_id, body.get("title"),
|
||||
description=body.get("description"),
|
||||
status=body.get("status"),
|
||||
priority=body.get("priority"),
|
||||
task_id=body.get("task_id"),
|
||||
due_at=body.get("due_at"),
|
||||
sort_order=body.get("sort_order"))
|
||||
except ValueError as exc:
|
||||
self._send_json(400, {"error": str(exc)})
|
||||
return
|
||||
except Exception as exc:
|
||||
self._send_json(502, {"error": str(exc)[:500]})
|
||||
return
|
||||
finally:
|
||||
conn.close()
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, project_id, "todo_created", todo["id"])
|
||||
self._send_json(201, todo)
|
||||
|
||||
def _todos_get(self, project_id, todo_id):
|
||||
"""单条待办:行不存在或属于别的项目时一律 404。"""
|
||||
conn = self._db()
|
||||
try:
|
||||
try:
|
||||
todo = _todo_get(conn, todo_id)
|
||||
except Exception as exc:
|
||||
self._send_json(502, {"error": str(exc)[:500]})
|
||||
return
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
if todo is None or todo["project_id"] != project_id:
|
||||
self._send_json(404, {"error": "todo not found"})
|
||||
return
|
||||
self._send_json(200, todo)
|
||||
|
||||
def _todos_update(self, user_id, project_id, todo_id):
|
||||
"""PATCH 待办:只改 body 里给出的字段;行不存在或
|
||||
属于别的项目时一律 404;校验失败 400。"""
|
||||
body, err = self._read_json()
|
||||
if err:
|
||||
self._send_json(400, {"error": err})
|
||||
return
|
||||
conn = self._db()
|
||||
try:
|
||||
try:
|
||||
todo = _todo_update(conn, todo_id, body or {})
|
||||
except ValueError as exc:
|
||||
self._send_json(400, {"error": str(exc)})
|
||||
return
|
||||
except Exception as exc:
|
||||
self._send_json(502, {"error": str(exc)[:500]})
|
||||
return
|
||||
finally:
|
||||
conn.close()
|
||||
if todo is None or todo["project_id"] != project_id:
|
||||
self._send_json(404, {"error": "todo not found"})
|
||||
return
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, project_id, "todo_updated", todo_id)
|
||||
self._send_json(200, todo)
|
||||
|
||||
def _todos_delete(self, user_id, project_id, todo_id):
|
||||
"""DELETE 待办(硬删除):行不存在或属于别的项目
|
||||
时一律 404;已取消是软关闭,不走这条。"""
|
||||
conn = self._db()
|
||||
try:
|
||||
try:
|
||||
todo = _todo_delete(conn, todo_id)
|
||||
except Exception as exc:
|
||||
self._send_json(502, {"error": str(exc)[:500]})
|
||||
return
|
||||
finally:
|
||||
conn.close()
|
||||
if todo is None or todo["project_id"] != project_id:
|
||||
self._send_json(404, {"error": "todo not found"})
|
||||
return
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, project_id, "todo_deleted", todo_id)
|
||||
self._send_json(200, todo)
|
||||
|
||||
def _automation_cancel(self, user_id, project_id, automation_id):
|
||||
"""取消自动化(软删除):行不存在或属于别的项目时一
|
||||
律 404(不泄露跨项目存在性);已取消时 409。"""
|
||||
|
||||
Reference in new issue
Block a user