Add case_delete: MCP tool + DELETE endpoint + DB helper
Wires a new case-deletion path across the three layers that needed it: - db.delete_case(case_id) — single SQL DELETE; documents, chunks, and qa_results cascade via existing schema FKs, audit_log nullifies. - cases_tools.case_delete(case_number, remove_files=False) — MCP tool wrapper. File tree on disk is kept by default (audit trail); pass remove_files=True for a hard delete. - DELETE /api/cases?case_number=... — FastAPI endpoint taking the case number as a QUERY param rather than a path segment. Case numbers like "1000/0426" can't be passed through a path parameter because FastAPI routing decodes %2F before matching, so a query param is the only shape that works for historical data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,12 @@ async def case_update(
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def case_delete(case_number: str, remove_files: bool = False) -> str:
|
||||
"""מחיקת תיק ערר. קבצים בדיסק נשארים אלא אם remove_files=true."""
|
||||
return await cases.case_delete(case_number, remove_files)
|
||||
|
||||
|
||||
# Documents
|
||||
@mcp.tool()
|
||||
async def document_upload(
|
||||
|
||||
@@ -557,6 +557,16 @@ async def list_cases(status: str | None = None, limit: int = 50) -> list[dict]:
|
||||
return [_row_to_case(r) for r in rows]
|
||||
|
||||
|
||||
async def delete_case(case_id: UUID) -> bool:
|
||||
"""Delete a case. Dependent rows in documents/document_chunks/qa_results
|
||||
cascade automatically (schema-level ON DELETE CASCADE); audit_log rows
|
||||
nullify their case_id reference. Returns True if a row was deleted."""
|
||||
pool = await get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
result = await conn.execute("DELETE FROM cases WHERE id = $1", case_id)
|
||||
return result.endswith(" 1")
|
||||
|
||||
|
||||
async def update_case(case_id: UUID, **fields) -> dict | None:
|
||||
if not fields:
|
||||
return await get_case(case_id)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
@@ -214,3 +215,37 @@ async def case_update(
|
||||
)
|
||||
|
||||
return json.dumps(updated, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def case_delete(case_number: str, remove_files: bool = False) -> str:
|
||||
"""מחיקת תיק ערר. מסיר את התיק מ-DB עם cascade לכל המסמכים והטענות.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
remove_files: האם למחוק גם את תיקיית הדיסק (drafts, git repo).
|
||||
ברירת מחדל False — ה-DB נמחק אבל הקבצים נשמרים לגיבוי.
|
||||
"""
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return json.dumps(
|
||||
{"deleted": False, "reason": f"תיק {case_number} לא נמצא."},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
ok = await db.delete_case(case_id)
|
||||
|
||||
result = {
|
||||
"deleted": ok,
|
||||
"case_number": case_number,
|
||||
"case_id": str(case_id),
|
||||
"removed_files": False,
|
||||
}
|
||||
|
||||
if ok and remove_files:
|
||||
case_dir = config.find_case_dir(case_number)
|
||||
if case_dir.exists():
|
||||
shutil.rmtree(case_dir, ignore_errors=True)
|
||||
result["removed_files"] = True
|
||||
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
16
web/app.py
16
web/app.py
@@ -1135,6 +1135,22 @@ async def api_case_update(case_number: str, req: CaseUpdateRequest):
|
||||
raise HTTPException(404, result)
|
||||
|
||||
|
||||
@app.delete("/api/cases")
|
||||
async def api_case_delete(case_number: str, remove_files: bool = False):
|
||||
"""Delete a case, identified by case_number in the query string.
|
||||
|
||||
Uses a query param (not a path segment) because case numbers may contain
|
||||
characters like `/` that FastAPI path routing cannot capture even when
|
||||
URL-encoded (%2F). Dependent documents/chunks/qa_results cascade via
|
||||
FK ON DELETE CASCADE; audit_log rows nullify their case_id.
|
||||
Pass `remove_files=true` to also rm -rf the on-disk case directory."""
|
||||
result = await cases_tools.case_delete(case_number, remove_files)
|
||||
data = json.loads(result)
|
||||
if not data.get("deleted"):
|
||||
raise HTTPException(404, data.get("reason", f"תיק {case_number} לא נמצא"))
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/api/cases/{case_number}/status")
|
||||
async def api_case_status(case_number: str):
|
||||
"""Get full workflow status for a case."""
|
||||
|
||||
Reference in New Issue
Block a user