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

Commit 16774f5

Browse files
committed
Implement basic soft deletion for workspaces
this adds a `deleted_at` column to workspaces that implements a basic soft-deletion mechanism. All relevant queries have been modified to reflect this. At the moment, there is no hard deletion of workspaces; this will be implemented in the future. We also have no way of showing "archived" or "soft-deleted" workspaces. This will come in due time. Signed-off-by: Juan Antonio Osorio <ozz@stacklok.com>
1 parent 28af062 commit 16774f5

File tree

5 files changed

+95
-6
lines changed

5 files changed

+95
-6
lines changed
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;")

src/codegate/api/v1.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,11 @@ async def create_workspace(request: v1_models.CreateWorkspaceRequest) -> v1_mode
8383
)
8484
async def delete_workspace(workspace_name: str):
8585
"""Delete a workspace by name."""
86-
raise NotImplementedError
86+
try:
87+
await wscrud.soft_delete_workspace(workspace_name)
88+
except crud.WorkspaceDoesNotExistError:
89+
return HTTPException(status_code=404, detail="Workspace does not exist")
90+
except Exception:
91+
return HTTPException(status_code=500, detail="Internal server error")
92+
93+
return Response(status_code=204)

src/codegate/db/connection.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ async def get_workspaces(self) -> List[WorkspaceActive]:
387387
w.id, w.name, s.active_workspace_id
388388
FROM workspaces w
389389
LEFT JOIN sessions s ON w.id = s.active_workspace_id
390+
WHERE w.deleted_at IS NULL
390391
"""
391392
)
392393
workspaces = await self._execute_select_pydantic_model(WorkspaceActive, sql)
@@ -398,7 +399,7 @@ async def get_workspace_by_name(self, name: str) -> List[Workspace]:
398399
SELECT
399400
id, name
400401
FROM workspaces
401-
WHERE name = :name
402+
WHERE name = :name AND deleted_at IS NULL
402403
"""
403404
)
404405
conditions = {"name": name}
@@ -430,6 +431,21 @@ async def get_active_workspace(self) -> Optional[ActiveWorkspace]:
430431
active_workspace = await self._execute_select_pydantic_model(ActiveWorkspace, sql)
431432
return active_workspace[0] if active_workspace else None
432433

434+
async def soft_delete_workspace(self, workspace_id: str) -> Optional[Workspace]:
435+
sql = text(
436+
"""
437+
UPDATE workspaces
438+
SET deleted_at = CURRENT_TIMESTAMP
439+
WHERE id = :id
440+
RETURNING *
441+
"""
442+
)
443+
conditions = {"id": workspace_id}
444+
deleted_workspace = await self._exec_select_conditions_to_pydantic(
445+
Workspace, sql, conditions, should_raise=True
446+
)
447+
return deleted_workspace[0] if deleted_workspace else None
448+
433449

434450
def init_db_sync(db_path: Optional[str] = None):
435451
"""DB will be initialized in the constructor in case it doesn't exist."""

src/codegate/pipeline/cli/commands.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(self):
4646
"list": self._list_workspaces,
4747
"add": self._add_workspace,
4848
"activate": self._activate_workspace,
49+
"remove": self._remove_workspace,
4950
}
5051

5152
async def _list_workspaces(self, *args: List[str]) -> str:
@@ -104,6 +105,25 @@ async def _activate_workspace(self, args: List[str]) -> str:
104105
return "An error occurred while activating the workspace"
105106
return f"Workspace **{workspace_name}** has been activated"
106107

108+
async def _remove_workspace(self, args: List[str]) -> str:
109+
"""
110+
Remove a workspace
111+
"""
112+
if args is None or len(args) == 0:
113+
return "Please provide a name. Use `codegate workspace remove workspace_name`"
114+
115+
workspace_name = args[0]
116+
if not workspace_name:
117+
return "Please provide a name. Use `codegate workspace remove workspace_name`"
118+
119+
try:
120+
await self.workspace_crud.soft_delete_workspace(workspace_name)
121+
except crud.WorkspaceDoesNotExistError:
122+
return f"Workspace **{workspace_name}** does not exist"
123+
except Exception:
124+
return "An error occurred while removing the workspace"
125+
return f"Workspace **{workspace_name}** has been removed"
126+
107127
async def run(self, args: List[str]) -> str:
108128
if not args:
109129
return "Please provide a command. Use `codegate workspace -h` to see available commands"

src/codegate/workspaces/crud.py

Lines changed: 20 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:
@@ -82,3 +78,23 @@ async def activate_workspace(self, workspace_name: str):
8278
db_recorder = DbRecorder()
8379
await db_recorder.update_session(session)
8480
return
81+
82+
async def soft_delete_workspace(self, workspace_name: str):
83+
"""
84+
Soft delete a workspace
85+
"""
86+
if workspace_name == "default":
87+
raise WorkspaceCrudError("Cannot delete default workspace.")
88+
89+
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
90+
if not selected_workspace:
91+
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
92+
93+
# Check if workspace is active, if it is, make the default workspace active
94+
active_workspace = await self._db_reader.get_active_workspace()
95+
if active_workspace and active_workspace.workspace_id == selected_workspace.id:
96+
await self.activate_workspace("default")
97+
98+
db_recorder = DbRecorder()
99+
_ = await db_recorder.soft_delete_workspace(selected_workspace)
100+
return

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy