diff --git a/apps/api/src/api/workspaces.py b/apps/api/src/api/workspaces.py index 61477ed..5bdda47 100644 --- a/apps/api/src/api/workspaces.py +++ b/apps/api/src/api/workspaces.py @@ -67,6 +67,41 @@ async def list_all_workspaces( ] +@all_workspaces_router.delete("/{workspace_id}") +async def delete_workspace_top_level( + workspace_id: uuid.UUID, + force: bool = Query(False), + user_id: uuid.UUID = Depends(get_current_user_id), + session: AsyncSession = Depends(get_db_session), +) -> dict: + """Delete a workspace via top-level path.""" + workspace = await session.get(Workspace, workspace_id) + if not workspace or workspace.user_id != user_id: + raise HTTPException(status_code=404, detail="Workspace not found") + + manager = WorkspaceManager() + try: + await manager.delete(workspace, force=force, session=session) + await session.commit() + except WorkspaceHasInstancesError as exc: + await session.rollback() + raise HTTPException( + status_code=409, + detail={ + "message": "Workspace has running tool instances", + "instances": [{"id": str(i.id), "name": i.name} for i in exc.instances], + }, + ) from exc + except Exception as exc: + await session.rollback() + logger.error("Failed to delete workspace: %s", exc) + raise HTTPException( + status_code=500, detail="Failed to delete workspace" + ) from exc + + return {"status": "deleted"} + + @all_workspaces_router.post("/") async def create_workspace_top_level( data: dict, diff --git a/apps/api/src/services/git_service.py b/apps/api/src/services/git_service.py index b076ede..8e81f10 100644 --- a/apps/api/src/services/git_service.py +++ b/apps/api/src/services/git_service.py @@ -147,7 +147,9 @@ class GitService: os.unlink(key_path) @staticmethod - def branch_exists_remotely(path: str, branch: str, ssh_key: str | None = None) -> bool: + def branch_exists_remotely( + path: str, branch: str, ssh_key: str | None = None + ) -> bool: """Check if a branch exists on the remote. Args: diff --git a/apps/web/src/api/workspaces.ts b/apps/web/src/api/workspaces.ts index 9636b7c..c032f54 100644 --- a/apps/web/src/api/workspaces.ts +++ b/apps/web/src/api/workspaces.ts @@ -71,13 +71,11 @@ export async function updateWorkspace( } export async function deleteWorkspace( - projectId: string, - repoId: string, workspaceId: string, force = false, ): Promise<{ status: string }> { const response = await apiClient.delete<{ status: string }>( - `${workspaceUrl(projectId, repoId, workspaceId)}?force=${force}`, + `/workspaces/${workspaceId}/?force=${force}`, ); return response.data; } diff --git a/apps/web/src/hooks/use-workspace-actions.ts b/apps/web/src/hooks/use-workspace-actions.ts index 52b5d99..968c163 100644 --- a/apps/web/src/hooks/use-workspace-actions.ts +++ b/apps/web/src/hooks/use-workspace-actions.ts @@ -17,8 +17,6 @@ export interface UseWorkspaceActionsResult { data: CreateWorkspaceRequest, ) => Promise; delete: ( - projectId: string, - repoId: string, workspace: Workspace, onRefresh: () => Promise, ) => Promise; @@ -61,14 +59,12 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult { const deleteAction = useCallback( async ( - projectId: string, - repoId: string, workspace: Workspace, onRefresh: () => Promise, ) => { setLoadingId(workspace.id); try { - await deleteWorkspace(projectId, repoId, workspace.id); + await deleteWorkspace(workspace.id); await onRefresh(); } catch (err) { const error = err as ApiError; @@ -81,7 +77,7 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult { `\n\nDelete workspace and all instances?`, ); if (confirmed) { - await deleteWorkspace(projectId, repoId, workspace.id, true); + await deleteWorkspace(workspace.id, true); await onRefresh(); } } else { @@ -118,7 +114,7 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult { `${message}\n\nDelete this workspace?`, ); if (confirmed) { - await deleteWorkspace(projectId, repoId, workspace.id, true); + await deleteWorkspace(workspace.id, true); await onRefresh(); } } else { diff --git a/apps/web/src/pages/projects.tsx b/apps/web/src/pages/projects.tsx index 103d510..9f71b29 100644 --- a/apps/web/src/pages/projects.tsx +++ b/apps/web/src/pages/projects.tsx @@ -124,15 +124,11 @@ export const ProjectsPage = () => { } }; - const handleDeleteWorkspace = async ( - projectId: string, - repoId: string, - workspace: WorkspaceSummary, - ) => { + const handleDeleteWorkspace = async (workspace: WorkspaceSummary) => { if (!confirm(`Delete workspace "${workspace.name}"?`)) return; setWorkspaceLoading(workspace.id); try { - await deleteWorkspace(projectId, repoId, workspace.id); + await deleteWorkspace(workspace.id); reload(); } catch (err) { alert(err instanceof Error ? err.message : "Failed to delete workspace"); @@ -187,7 +183,7 @@ export const ProjectsPage = () => { if (action === "sync") { void handleSyncWorkspace(project.id, repoId, workspace); } else if (action === "delete") { - void handleDeleteWorkspace(project.id, repoId, workspace); + void handleDeleteWorkspace(workspace); } }} workspaceLoading={workspaceLoading} diff --git a/apps/web/src/pages/workspaces.tsx b/apps/web/src/pages/workspaces.tsx index 9802f17..f688a2e 100644 --- a/apps/web/src/pages/workspaces.tsx +++ b/apps/web/src/pages/workspaces.tsx @@ -18,12 +18,7 @@ export function WorkspacesPage() { const actions = useWorkspaceActions(); const handleDelete = async (workspace: Workspace) => { - await actions.delete( - workspace.project_id, - workspace.repo_id, - workspace, - refresh, - ); + await actions.delete(workspace, refresh); }; const handleSync = async (workspace: Workspace) => {