diff --git a/mcp-server/src/legal_mcp/server.py b/mcp-server/src/legal_mcp/server.py index 54dc127..0723859 100644 --- a/mcp-server/src/legal_mcp/server.py +++ b/mcp-server/src/legal_mcp/server.py @@ -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( diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index c577c57..0362264 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -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) diff --git a/mcp-server/src/legal_mcp/tools/cases.py b/mcp-server/src/legal_mcp/tools/cases.py index 794c4c2..a5b71cb 100644 --- a/mcp-server/src/legal_mcp/tools/cases.py +++ b/mcp-server/src/legal_mcp/tools/cases.py @@ -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) diff --git a/web/app.py b/web/app.py index 8dcbbe1..b9dc37b 100644 --- a/web/app.py +++ b/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."""