Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit bb63b3c

Browse files
author
Luke Hinds
authored
Merge branch 'main' into sqlite-vec
2 parents 44fcd1d + da7770a commit bb63b3c

File tree

7 files changed

+129
-9
lines changed

7 files changed

+129
-9
lines changed

api/openapi.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,13 @@
285285
}
286286
],
287287
"responses": {
288-
"204": {
289-
"description": "Successful Response"
288+
"200": {
289+
"description": "Successful Response",
290+
"content": {
291+
"application/json": {
292+
"schema": {}
293+
}
294+
}
290295
},
291296
"422": {
292297
"description": "Validation Error",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""add soft delete
2+
3+
Revision ID: 8e4b4b8d1a88
4+
Revises: 5c2f3eee5f90
5+
Create Date: 2025-01-20 14:08:40.851647
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision: str = "8e4b4b8d1a88"
15+
down_revision: Union[str, None] = "5c2f3eee5f90"
16+
branch_labels: Union[str, Sequence[str], None] = None
17+
depends_on: Union[str, Sequence[str], None] = None
18+
19+
20+
def upgrade() -> None:
21+
op.execute(
22+
"""
23+
ALTER TABLE workspaces
24+
ADD COLUMN deleted_at DATETIME DEFAULT NULL;
25+
"""
26+
)
27+
28+
29+
def downgrade() -> None:
30+
op.execute("ALTER TABLE workspaces DROP COLUMN deleted_at;")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""merging system prompt and soft-deletes
2+
3+
Revision ID: e6227073183d
4+
Revises: 8e4b4b8d1a88, a692c8b52308
5+
Create Date: 2025-01-20 16:08:40.645298
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
# revision identifiers, used by Alembic.
12+
revision: str = "e6227073183d"
13+
down_revision: Union[str, None] = ("8e4b4b8d1a88", "a692c8b52308")
14+
branch_labels: Union[str, Sequence[str], None] = None
15+
depends_on: Union[str, Sequence[str], None] = None
16+
17+
18+
def upgrade() -> None:
19+
pass
20+
21+
22+
def downgrade() -> None:
23+
pass

src/codegate/api/v1.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,14 @@ async def create_workspace(request: v1_models.CreateWorkspaceRequest) -> v1_mode
7979
"/workspaces/{workspace_name}",
8080
tags=["Workspaces"],
8181
generate_unique_id_function=uniq_name,
82-
status_code=204,
8382
)
8483
async def delete_workspace(workspace_name: str):
8584
"""Delete a workspace by name."""
86-
raise NotImplementedError
85+
try:
86+
_ = await wscrud.soft_delete_workspace(workspace_name)
87+
except crud.WorkspaceDoesNotExistError:
88+
raise HTTPException(status_code=404, detail="Workspace does not exist")
89+
except Exception:
90+
raise HTTPException(status_code=500, detail="Internal server error")
91+
92+
return Response(status_code=204)

src/codegate/db/connection.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,20 @@ async def update_session(self, session: Session) -> Optional[Session]:
304304
active_session = await self._execute_update_pydantic_model(session, sql, should_raise=True)
305305
return active_session
306306

307+
async def soft_delete_workspace(self, workspace: Workspace) -> Optional[Workspace]:
308+
sql = text(
309+
"""
310+
UPDATE workspaces
311+
SET deleted_at = CURRENT_TIMESTAMP
312+
WHERE id = :id
313+
RETURNING *
314+
"""
315+
)
316+
deleted_workspace = await self._execute_update_pydantic_model(
317+
workspace, sql, should_raise=True
318+
)
319+
return deleted_workspace
320+
307321

308322
class DbReader(DbCodeGate):
309323

@@ -401,6 +415,7 @@ async def get_workspaces(self) -> List[WorkspaceActive]:
401415
w.id, w.name, s.active_workspace_id
402416
FROM workspaces w
403417
LEFT JOIN sessions s ON w.id = s.active_workspace_id
418+
WHERE w.deleted_at IS NULL
404419
"""
405420
)
406421
workspaces = await self._execute_select_pydantic_model(WorkspaceActive, sql)
@@ -412,7 +427,7 @@ async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
412427
SELECT
413428
id, name, system_prompt
414429
FROM workspaces
415-
WHERE name = :name
430+
WHERE name = :name AND deleted_at IS NULL
416431
"""
417432
)
418433
conditions = {"name": name}

src/codegate/pipeline/cli/commands.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def subcommands(self) -> Dict[str, Callable[[List[str]], Awaitable[str]]]:
153153
"list": self._list_workspaces,
154154
"add": self._add_workspace,
155155
"activate": self._activate_workspace,
156+
"remove": self._remove_workspace,
156157
}
157158

158159
async def _list_workspaces(self, flags: Dict[str, str], args: List[str]) -> str:
@@ -211,6 +212,25 @@ async def _activate_workspace(self, flags: Dict[str, str], args: List[str]) -> s
211212
return "An error occurred while activating the workspace"
212213
return f"Workspace **{workspace_name}** has been activated"
213214

215+
async def _remove_workspace(self, flags: Dict[str, str], args: List[str]) -> str:
216+
"""
217+
Remove a workspace
218+
"""
219+
if args is None or len(args) == 0:
220+
return "Please provide a name. Use `codegate workspace remove workspace_name`"
221+
222+
workspace_name = args[0]
223+
if not workspace_name:
224+
return "Please provide a name. Use `codegate workspace remove workspace_name`"
225+
226+
try:
227+
await self.workspace_crud.soft_delete_workspace(workspace_name)
228+
except crud.WorkspaceDoesNotExistError:
229+
return f"Workspace **{workspace_name}** does not exist"
230+
except Exception:
231+
return "An error occurred while removing the workspace"
232+
return f"Workspace **{workspace_name}** has been removed"
233+
214234
@property
215235
def help(self) -> str:
216236
return (

src/codegate/workspaces/crud.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ async def _is_workspace_active(
6868
async def activate_workspace(self, workspace_name: str):
6969
"""
7070
Activate a workspace
71-
72-
Will return:
73-
- True if the workspace was activated
74-
- False if the workspace is already active or does not exist
7571
"""
7672
is_active, session, workspace = await self._is_workspace_active(workspace_name)
7773
if is_active:
@@ -100,6 +96,31 @@ async def update_workspace_system_prompt(
10096
updated_workspace = await db_recorder.update_workspace(workspace_update)
10197
return updated_workspace
10298

99+
async def soft_delete_workspace(self, workspace_name: str):
100+
"""
101+
Soft delete a workspace
102+
"""
103+
if workspace_name == "":
104+
raise WorkspaceCrudError("Workspace name cannot be empty.")
105+
if workspace_name == "default":
106+
raise WorkspaceCrudError("Cannot delete default workspace.")
107+
108+
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
109+
if not selected_workspace:
110+
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
111+
112+
# Check if workspace is active, if it is, make the default workspace active
113+
active_workspace = await self._db_reader.get_active_workspace()
114+
if active_workspace and active_workspace.id == selected_workspace.id:
115+
raise WorkspaceCrudError("Cannot delete active workspace.")
116+
117+
db_recorder = DbRecorder()
118+
try:
119+
_ = await db_recorder.soft_delete_workspace(selected_workspace)
120+
except Exception:
121+
raise WorkspaceCrudError(f"Error deleting workspace {workspace_name}")
122+
return
123+
103124
async def get_workspace_by_name(self, workspace_name: str) -> Workspace:
104125
workspace = await self._db_reader.get_workspace_by_name(workspace_name)
105126
if not workspace:

0 commit comments

Comments
 (0)