Skip to content

Commit 1205448

Browse files
initial commit
1 parent 445c6de commit 1205448

File tree

5 files changed

+164
-6
lines changed

5 files changed

+164
-6
lines changed

services/web/server/src/simcore_service_webserver/trash/_service.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ..folders import folders_trash_service
1313
from ..products import products_service
1414
from ..projects import projects_trash_service
15+
from ..workspaces import workspaces_trash_service
1516
from .settings import get_plugin_settings
1617

1718
_logger = logging.getLogger(__name__)
@@ -99,13 +100,52 @@ async def _empty_explicitly_trashed_folders_and_content(
99100
)
100101

101102

103+
async def _empty_trashed_workspaces_and_content(
104+
app: web.Application, product_name: ProductName, user_id: UserID
105+
):
106+
trashed_workspaces_ids = await workspaces_trash_service.list_trashed_workspaces(
107+
app=app, product_name=product_name, user_id=user_id
108+
)
109+
110+
with log_context(
111+
_logger,
112+
logging.DEBUG,
113+
"Deleting %s trashed workspaces (and all its content)",
114+
len(trashed_workspaces_ids),
115+
):
116+
for workspace_id in trashed_workspaces_ids:
117+
try:
118+
await workspaces_trash_service.delete_trashed_workspace(
119+
app,
120+
product_name=product_name,
121+
user_id=user_id,
122+
workspace_id=workspace_id,
123+
)
124+
125+
except Exception as exc: # pylint: disable=broad-exception-caught
126+
_logger.warning(
127+
**create_troubleshotting_log_kwargs(
128+
"Error deleting a trashed workspace (and content) while emptying trash.",
129+
error=exc,
130+
error_context={
131+
"workspace_id": workspace_id,
132+
"product_name": product_name,
133+
"user_id": user_id,
134+
},
135+
tip=_TIP,
136+
)
137+
)
138+
139+
102140
async def safe_empty_trash(
103141
app: web.Application, *, product_name: ProductName, user_id: UserID
104142
):
105143
await _empty_explicitly_trashed_projects(app, product_name, user_id)
106144

107145
await _empty_explicitly_trashed_folders_and_content(app, product_name, user_id)
108146

147+
await _empty_trashed_workspaces_and_content(app, product_name, user_id)
148+
109149

110150
async def safe_delete_expired_trash_as_admin(app: web.Application) -> None:
111151
settings = get_plugin_settings(app)

services/web/server/src/simcore_service_webserver/workspaces/_trash_rest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ..login.decorators import get_user_id, login_required
1212
from ..products import products_web
1313
from ..security.decorators import permission_required
14-
from . import _trash_services
14+
from . import _trash_service
1515
from ._common.exceptions_handlers import handle_plugin_requests_exceptions
1616
from ._common.models import WorkspacesPathParams, WorkspaceTrashQueryParams
1717

@@ -33,7 +33,7 @@ async def trash_workspace(request: web.Request):
3333
WorkspaceTrashQueryParams, request
3434
)
3535

36-
await _trash_services.trash_workspace(
36+
await _trash_service.trash_workspace(
3737
request.app,
3838
product_name=product_name,
3939
user_id=user_id,
@@ -53,7 +53,7 @@ async def untrash_workspace(request: web.Request):
5353
product_name = products_web.get_product_name(request)
5454
path_params = parse_request_path_parameters_as(WorkspacesPathParams, request)
5555

56-
await _trash_services.untrash_workspace(
56+
await _trash_service.untrash_workspace(
5757
request.app,
5858
product_name=product_name,
5959
user_id=user_id,

services/web/server/src/simcore_service_webserver/workspaces/_trash_services.py renamed to services/web/server/src/simcore_service_webserver/workspaces/_trash_service.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import logging
2+
from datetime import datetime
23

34
import arrow
45
from aiohttp import web
6+
from common_library.pagination_tools import iter_pagination_params
7+
from models_library.basic_types import IDStr
58
from models_library.folders import FolderID
69
from models_library.products import ProductName
710
from models_library.projects import ProjectID
11+
from models_library.rest_ordering import OrderBy, OrderDirection
12+
from models_library.rest_pagination import MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE
813
from models_library.users import UserID
9-
from models_library.workspaces import WorkspaceID, WorkspaceUpdates
14+
from models_library.workspaces import (
15+
UserWorkspaceWithAccessRights,
16+
WorkspaceID,
17+
WorkspaceUpdates,
18+
)
1019
from simcore_postgres_database.utils_repos import transaction_context
1120

1221
from ..db.plugin import get_asyncpg_engine
1322
from ..folders._trash_service import trash_folder, untrash_folder
1423
from ..projects._trash_service import trash_project, untrash_project
1524
from . import _workspaces_repository, _workspaces_service
25+
from .errors import WorkspaceNotTrashedError
1626

1727
_logger = logging.getLogger(__name__)
1828

@@ -127,3 +137,94 @@ async def untrash_workspace(
127137
await untrash_project(
128138
app, product_name=product_name, user_id=user_id, project_id=project_id
129139
)
140+
141+
142+
# delete_trashed_workspace,
143+
144+
145+
def _can_delete(
146+
workspace: UserWorkspaceWithAccessRights,
147+
user_id: UserID,
148+
until_equal_datetime: datetime | None,
149+
) -> bool:
150+
return bool(
151+
workspace.trashed
152+
and (until_equal_datetime is None or workspace.trashed < until_equal_datetime)
153+
and workspace.my_access_rights.delete
154+
and workspace.trashed_by == user_id
155+
)
156+
157+
158+
async def delete_trashed_workspace(
159+
app: web.Application,
160+
*,
161+
product_name: ProductName,
162+
user_id: UserID,
163+
workspace_id: WorkspaceID,
164+
until_equal_datetime: datetime | None = None,
165+
) -> None:
166+
167+
workspace = await _workspaces_service.get_workspace(
168+
app,
169+
user_id=user_id,
170+
product_name=product_name,
171+
workspace_id=workspace_id,
172+
)
173+
174+
if not _can_delete(
175+
workspace,
176+
user_id=user_id,
177+
until_equal_datetime=until_equal_datetime,
178+
):
179+
raise WorkspaceNotTrashedError(
180+
workspace_id=workspace_id,
181+
user_id=user_id,
182+
reason="Cannot delete trashed workspace since it does not fit current criteria",
183+
)
184+
185+
# NOTE: this function deletes workspace AND its content recursively!
186+
await _workspaces_service.delete_workspace(
187+
app,
188+
user_id=user_id,
189+
product_name=product_name,
190+
workspace_id=workspace_id,
191+
)
192+
193+
194+
async def list_trashed_workspaces(
195+
app: web.Application,
196+
product_name: ProductName,
197+
user_id: UserID,
198+
until_equal_datetime: datetime | None = None,
199+
) -> list[WorkspaceID]:
200+
trashed_workspace_ids: list[WorkspaceID] = []
201+
202+
for page_params in iter_pagination_params(limit=MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE):
203+
(
204+
page_params.total_number_of_items,
205+
workspaces,
206+
) = await _workspaces_service.list_workspaces(
207+
app,
208+
user_id=user_id,
209+
product_name=product_name,
210+
filter_trashed=True,
211+
filter_by_text=None,
212+
offset=page_params.offset,
213+
limit=page_params.limit,
214+
order_by=OrderBy(field=IDStr("trashed"), direction=OrderDirection.ASC),
215+
)
216+
217+
# NOTE: Applying POST-FILTERING
218+
trashed_workspace_ids.extend(
219+
[
220+
ws.workspace_id
221+
for ws in workspaces
222+
if _can_delete(
223+
ws,
224+
user_id=user_id,
225+
until_equal_datetime=until_equal_datetime,
226+
)
227+
]
228+
)
229+
230+
return trashed_workspace_ids

services/web/server/src/simcore_service_webserver/workspaces/errors.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from ..errors import WebServerBaseError
22

33

4-
class WorkspacesValueError(WebServerBaseError, ValueError):
5-
...
4+
class WorkspacesValueError(WebServerBaseError, ValueError): ...
5+
6+
7+
class WorkspacesRuntimeError(WebServerBaseError, RuntimeError): ...
68

79

810
class WorkspaceNotFoundError(WorkspacesValueError):
@@ -22,3 +24,7 @@ class WorkspaceGroupNotFoundError(WorkspacesValueError):
2224

2325
class WorkspaceFolderInconsistencyError(WorkspacesValueError):
2426
msg_template = "Folder {folder_id} does not exists in the workspace {workspace_id}"
27+
28+
29+
class WorkspaceNotTrashedError(WorkspacesRuntimeError):
30+
msg_template = "Cannot delete workspace {workspace_id} since it was not trashed first: {reason}"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ._trash_service import (
2+
delete_trashed_workspace,
3+
list_trashed_workspaces,
4+
)
5+
6+
__all__: tuple[str, ...] = (
7+
"delete_trashed_workspace",
8+
"list_trashed_workspaces",
9+
)
10+
11+
# nopycln: file

0 commit comments

Comments
 (0)