fix(backend): 修复全面审阅发现的核心问题
修复索引首次失败回滚、Vault 扫描边界、Markdown 代码围栏和 UTF-16 Citation 偏移。 收紧 Plugin 权限与 JSON Schema 校验,补齐 Note Move、Task、Attachment 和 Transcript Tool。 接入 OpenAI SSE 与 Ollama JSONL 真流式输出,修正 Provider PATCH 语义并限制运行时内存保留。 新增对应回归测试,后端测试增至 62 项。
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
from app import repository
|
||||
from app.contracts import Task, TaskStatus
|
||||
from app.database.db import connect, transaction
|
||||
from app.errors import ApiError
|
||||
|
||||
|
||||
def _now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def _task_from_row(row) -> Task:
|
||||
return Task(
|
||||
task_id=row["task_id"],
|
||||
title=row["title"],
|
||||
description=row["description"],
|
||||
status=TaskStatus(row["status"]),
|
||||
note_id=row["note_id"],
|
||||
due_at=datetime.fromisoformat(row["due_at"]) if row["due_at"] else None,
|
||||
created_at=datetime.fromisoformat(row["created_at"]),
|
||||
updated_at=datetime.fromisoformat(row["updated_at"]),
|
||||
)
|
||||
|
||||
|
||||
def create_task(
|
||||
*, title: str, description: str = "", note_id: str | None = None,
|
||||
due_at: datetime | None = None,
|
||||
) -> Task:
|
||||
if note_id and repository.get_note_record(note_id) is None:
|
||||
raise ApiError(404, "RESOURCE_NOT_FOUND", "note not found", {"note_id": note_id})
|
||||
task_id = f"task_{uuid4().hex}"
|
||||
now = _now()
|
||||
conn = connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO tasks
|
||||
(task_id, title, description, status, note_id, due_at, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
task_id, title, description, TaskStatus.todo.value, note_id,
|
||||
due_at.isoformat() if due_at else None, now.isoformat(), now.isoformat(),
|
||||
),
|
||||
)
|
||||
row = conn.execute("SELECT * FROM tasks WHERE task_id = ?", (task_id,)).fetchone()
|
||||
return _task_from_row(row)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_task(task_id: str) -> Task | None:
|
||||
conn = connect()
|
||||
try:
|
||||
row = conn.execute("SELECT * FROM tasks WHERE task_id = ?", (task_id,)).fetchone()
|
||||
return _task_from_row(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def list_tasks(*, limit: int, offset: int) -> tuple[list[Task], int]:
|
||||
conn = connect()
|
||||
try:
|
||||
total = conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM tasks ORDER BY updated_at DESC LIMIT ? OFFSET ?",
|
||||
(limit, offset),
|
||||
).fetchall()
|
||||
return [_task_from_row(row) for row in rows], total
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_task(task_id: str, values: dict[str, object]) -> Task:
|
||||
current = get_task(task_id)
|
||||
if current is None:
|
||||
raise ApiError(404, "RESOURCE_NOT_FOUND", "task not found", {"task_id": task_id})
|
||||
if "note_id" in values and values["note_id"]:
|
||||
note_id = str(values["note_id"])
|
||||
if repository.get_note_record(note_id) is None:
|
||||
raise ApiError(404, "RESOURCE_NOT_FOUND", "note not found", {"note_id": note_id})
|
||||
if values.get("title") is None:
|
||||
values.pop("title", None)
|
||||
if values.get("description") is None:
|
||||
values.pop("description", None)
|
||||
if values.get("status") is None:
|
||||
values.pop("status", None)
|
||||
|
||||
columns: list[str] = []
|
||||
params: list[object] = []
|
||||
for name, value in values.items():
|
||||
columns.append(f"{name} = ?")
|
||||
if isinstance(value, datetime):
|
||||
value = value.isoformat()
|
||||
elif isinstance(value, TaskStatus):
|
||||
value = value.value
|
||||
params.append(value)
|
||||
columns.append("updated_at = ?")
|
||||
params.append(_now().isoformat())
|
||||
params.append(task_id)
|
||||
conn = connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
conn.execute(
|
||||
f"UPDATE tasks SET {', '.join(columns)} WHERE task_id = ?",
|
||||
params,
|
||||
)
|
||||
row = conn.execute("SELECT * FROM tasks WHERE task_id = ?", (task_id,)).fetchone()
|
||||
return _task_from_row(row)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_task(task_id: str) -> bool:
|
||||
conn = connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
cursor = conn.execute("DELETE FROM tasks WHERE task_id = ?", (task_id,))
|
||||
return cursor.rowcount > 0
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def note_links() -> dict[str, str]:
|
||||
"""重建可再生 Note 索引前,暂存不可再生 Task 到 Note 的业务关联。"""
|
||||
conn = connect()
|
||||
try:
|
||||
return {
|
||||
row["task_id"]: row["note_id"]
|
||||
for row in conn.execute(
|
||||
"SELECT task_id, note_id FROM tasks WHERE note_id IS NOT NULL"
|
||||
)
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def restore_note_links(links: dict[str, str]) -> None:
|
||||
if not links:
|
||||
return
|
||||
conn = connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
for task_id, note_id in links.items():
|
||||
exists = conn.execute(
|
||||
"SELECT 1 FROM notes WHERE note_id = ?", (note_id,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET note_id = ? WHERE task_id = ?",
|
||||
(note_id, task_id),
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user