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:
2026-04-11 16:47:50 +00:00
parent 26d09d648f
commit 8989ad9a9b
4 changed files with 67 additions and 0 deletions

View File

@@ -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)