Skip to content

Commit 9583e82

Browse files
committed
feat: add DELETE /api/repos/{repo_id} endpoint
Cleans up FTS index before CASCADE-deleting the repository and all child rows (pages, graph, symbols, git metadata, decisions, etc.).
1 parent 888b0cd commit 9583e82

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

packages/server/src/repowise/server/routers/repos.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Page,
1818
Repository,
1919
)
20-
from repowise.server.deps import get_db_session, verify_api_key
20+
from repowise.server.deps import get_db_session, get_fts, verify_api_key
2121
from repowise.server.job_executor import execute_job
2222
from repowise.server.schemas import RepoCreate, RepoResponse, RepoStatsResponse, RepoUpdate
2323

@@ -94,6 +94,30 @@ async def update_repo(
9494
return RepoResponse.from_orm(repo)
9595

9696

97+
@router.delete("/{repo_id}")
98+
async def delete_repo(
99+
repo_id: str,
100+
session: AsyncSession = Depends(get_db_session), # noqa: B008
101+
fts=Depends(get_fts), # noqa: B008
102+
) -> dict:
103+
"""Delete a repository and all its data."""
104+
repo = await crud.get_repository(session, repo_id)
105+
if repo is None:
106+
raise HTTPException(status_code=404, detail="Repository not found")
107+
108+
# Collect page IDs before CASCADE deletes the Page rows
109+
page_ids = await crud.list_page_ids(session, repo_id)
110+
111+
# Clean up FTS index (FTS5 virtual table has no FK cascade)
112+
if fts is not None:
113+
await fts.delete_many(page_ids)
114+
115+
# Delete repository — CASCADE handles all child ORM tables
116+
await crud.delete_repository(session, repo_id)
117+
118+
return {"ok": True, "deleted_pages": len(page_ids)}
119+
120+
97121
@router.get("/{repo_id}/stats", response_model=RepoStatsResponse)
98122
async def get_repo_stats(
99123
repo_id: str,

tests/unit/server/test_repos.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,22 @@ async def test_full_resync_duplicate_returns_409(client: AsyncClient) -> None:
125125

126126
resp2 = await client.post(f"/api/repos/{repo['id']}/full-resync")
127127
assert resp2.status_code == 409
128+
129+
130+
@pytest.mark.asyncio
131+
async def test_delete_repo_success(client: AsyncClient) -> None:
132+
repo = await create_test_repo(client)
133+
resp = await client.delete(f"/api/repos/{repo['id']}")
134+
assert resp.status_code == 200
135+
data = resp.json()
136+
assert data["ok"] is True
137+
138+
# Verify repo is gone
139+
resp = await client.get(f"/api/repos/{repo['id']}")
140+
assert resp.status_code == 404
141+
142+
143+
@pytest.mark.asyncio
144+
async def test_delete_repo_not_found(client: AsyncClient) -> None:
145+
resp = await client.delete("/api/repos/nonexistent")
146+
assert resp.status_code == 404

0 commit comments

Comments
 (0)