Skip to content

Commit f70f1dc

Browse files
committed
folders trashed-by
1 parent f2b8be9 commit f70f1dc

File tree

6 files changed

+74
-107
lines changed

6 files changed

+74
-107
lines changed

packages/models-library/src/models_library/api_schemas_webserver/folders_v2.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from datetime import datetime
2-
from typing import NamedTuple
2+
from typing import Annotated, NamedTuple, Self
33

4-
from pydantic import ConfigDict, PositiveInt, field_validator
4+
from pydantic import ConfigDict, Field, PositiveInt, field_validator
55

66
from ..access_rights import AccessRights
77
from ..basic_types import IDStr
8-
from ..folders import FolderID
8+
from ..folders import FolderDB, FolderID
99
from ..groups import GroupID
10-
from ..users import UserID
1110
from ..utils.common_validators import null_or_none_str_to_none_validator
1211
from ..workspaces import WorkspaceID
1312
from ._base import InputSchema, OutputSchema
@@ -17,16 +16,34 @@ class FolderGet(OutputSchema):
1716
folder_id: FolderID
1817
parent_folder_id: FolderID | None = None
1918
name: str
19+
2020
created_at: datetime
2121
modified_at: datetime
2222
trashed_at: datetime | None
23-
# NOTE: by design a folder is constraint to trashed_by == owner
24-
# but we add this here for completeness
25-
trashed_by: UserID | None
23+
trashed_by: Annotated[
24+
GroupID | None, Field(description="The primary gid of the user who trashed")
25+
]
2626
owner: GroupID
2727
workspace_id: WorkspaceID | None
2828
my_access_rights: AccessRights
2929

30+
@classmethod
31+
def from_domain_model(
32+
cls, folder_db: FolderDB, user_folder_access_rights: AccessRights
33+
) -> Self:
34+
return cls(
35+
folder_id=folder_db.folder_id,
36+
parent_folder_id=folder_db.parent_folder_id,
37+
name=folder_db.name,
38+
created_at=folder_db.created,
39+
modified_at=folder_db.modified,
40+
trashed_at=folder_db.trashed,
41+
trashed_by=folder_db.trashed_by_primary_gid,
42+
owner=folder_db.created_by_gid,
43+
workspace_id=folder_db.workspace_id,
44+
my_access_rights=user_folder_access_rights,
45+
)
46+
3047

3148
class FolderGetPage(NamedTuple):
3249
items: list[FolderGet]

packages/models-library/src/models_library/folders.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class FolderDB(BaseModel):
5252

5353
trashed: datetime | None
5454
trashed_by: UserID | None
55+
trashed_by_primary_gid: GroupID | None
5556
trashed_explicitly: bool
5657

5758
user_id: UserID | None # owner?

services/web/server/src/simcore_service_webserver/folders/_folders_repository.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from simcore_postgres_database.models.folders_v2 import folders_v2
2929
from simcore_postgres_database.models.projects import projects
3030
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
31+
from simcore_postgres_database.models.users import users
3132
from simcore_postgres_database.utils_repos import (
3233
pass_or_acquire_connection,
3334
transaction_context,
@@ -48,7 +49,7 @@
4849
_unset: Final = UnSet()
4950

5051

51-
_SELECTION_ARGS = (
52+
_FOLDERS_SELECTION_COLS = (
5253
folders_v2.c.folder_id,
5354
folders_v2.c.name,
5455
folders_v2.c.parent_folder_id,
@@ -91,7 +92,7 @@ async def create(
9192
created=func.now(),
9293
modified=func.now(),
9394
)
94-
.returning(*_SELECTION_ARGS)
95+
.returning(*_FOLDERS_SELECTION_COLS)
9596
)
9697
row = await result.first()
9798
return FolderDB.model_validate(row)
@@ -109,7 +110,7 @@ def _create_private_workspace_query(
109110
)
110111
return (
111112
select(
112-
*_SELECTION_ARGS,
113+
*_FOLDERS_SELECTION_COLS,
113114
func.json_build_object(
114115
"read",
115116
sa.text("true"),
@@ -146,7 +147,8 @@ def _create_shared_workspace_query(
146147

147148
shared_workspace_query = (
148149
select(
149-
*_SELECTION_ARGS, workspace_access_rights_subquery.c.my_access_rights
150+
*_FOLDERS_SELECTION_COLS,
151+
workspace_access_rights_subquery.c.my_access_rights,
150152
)
151153
.select_from(
152154
folders_v2.join(
@@ -270,21 +272,28 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
270272
return cast(int, total_count), folders
271273

272274

275+
def _create_base_select_query(folder_id: FolderID, product_name: ProductName):
276+
return (
277+
select(
278+
*_FOLDERS_SELECTION_COLS,
279+
users.c.primary_gid.label("trashed_by_primary_gid"),
280+
)
281+
.select_from(folders_v2.outerjoin(users, folders_v2.c.trashed_by == users.c.id))
282+
.where(
283+
(folders_v2.c.product_name == product_name)
284+
& (folders_v2.c.folder_id == folder_id)
285+
)
286+
)
287+
288+
273289
async def get(
274290
app: web.Application,
275291
connection: AsyncConnection | None = None,
276292
*,
277293
folder_id: FolderID,
278294
product_name: ProductName,
279295
) -> FolderDB:
280-
query = (
281-
select(*_SELECTION_ARGS)
282-
.select_from(folders_v2)
283-
.where(
284-
(folders_v2.c.product_name == product_name)
285-
& (folders_v2.c.folder_id == folder_id)
286-
)
287-
)
296+
query = _create_base_select_query(folder_id=folder_id, product_name=product_name)
288297

289298
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
290299
result = await conn.execute(query)
@@ -309,16 +318,10 @@ async def get_for_user_or_workspace(
309318
user_id is not None and workspace_id is not None
310319
), "Both user_id and workspace_id cannot be provided at the same time. Please provide only one."
311320

312-
query = (
313-
select(*_SELECTION_ARGS)
314-
.select_from(folders_v2)
315-
.where(
316-
(folders_v2.c.product_name == product_name)
317-
& (folders_v2.c.folder_id == folder_id)
318-
)
319-
)
321+
query = _create_base_select_query(folder_id=folder_id, product_name=product_name)
320322

321323
if user_id:
324+
# ownership
322325
query = query.where(folders_v2.c.user_id == user_id)
323326
else:
324327
query = query.where(folders_v2.c.workspace_id == workspace_id)
@@ -366,7 +369,7 @@ async def update(
366369
query = (
367370
(folders_v2.update().values(modified=func.now(), **updated))
368371
.where(folders_v2.c.product_name == product_name)
369-
.returning(*_SELECTION_ARGS)
372+
.returning(*_FOLDERS_SELECTION_COLS)
370373
)
371374

372375
if isinstance(folders_id_or_ids, set):
@@ -583,4 +586,4 @@ async def get_folders_recursively(
583586
# Step 4: Execute the query to get all descendants
584587
final_query = select(folder_hierarchy_cte)
585588
result = await conn.stream(final_query)
586-
return [FolderID(row[0]) async for row in result]
589+
return cast(list[FolderID], [row.folder_id async for row in result])

services/web/server/src/simcore_service_webserver/folders/_folders_service.py

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,7 @@ async def create_folder(
8686
user_id=user_id if workspace_is_private else None,
8787
workspace_id=workspace_id,
8888
)
89-
return FolderGet(
90-
folder_id=folder_db.folder_id,
91-
parent_folder_id=folder_db.parent_folder_id,
92-
name=folder_db.name,
93-
created_at=folder_db.created,
94-
modified_at=folder_db.modified,
95-
trashed_at=folder_db.trashed,
96-
trashed_by=folder_db.trashed_by,
97-
owner=folder_db.created_by_gid,
98-
workspace_id=workspace_id,
99-
my_access_rights=user_folder_access_rights,
100-
)
89+
return FolderGet.from_domain_model(folder_db, user_folder_access_rights)
10190

10291

10392
async def get_folder(
@@ -130,18 +119,7 @@ async def get_folder(
130119
user_id=user_id if workspace_is_private else None,
131120
workspace_id=folder_db.workspace_id,
132121
)
133-
return FolderGet(
134-
folder_id=folder_db.folder_id,
135-
parent_folder_id=folder_db.parent_folder_id,
136-
name=folder_db.name,
137-
created_at=folder_db.created,
138-
modified_at=folder_db.modified,
139-
trashed_at=folder_db.trashed,
140-
trashed_by=folder_db.trashed_by,
141-
owner=folder_db.created_by_gid,
142-
workspace_id=folder_db.workspace_id,
143-
my_access_rights=user_folder_access_rights,
144-
)
122+
return FolderGet.from_domain_model(folder_db, user_folder_access_rights)
145123

146124

147125
async def list_folders(
@@ -181,17 +159,9 @@ async def list_folders(
181159
)
182160
return FolderGetPage(
183161
items=[
184-
FolderGet(
185-
folder_id=folder.folder_id,
186-
parent_folder_id=folder.parent_folder_id,
187-
name=folder.name,
188-
created_at=folder.created,
189-
modified_at=folder.modified,
190-
trashed_at=folder.trashed,
191-
trashed_by=folder.trashed_by,
192-
owner=folder.created_by_gid,
193-
workspace_id=folder.workspace_id,
194-
my_access_rights=folder.my_access_rights,
162+
FolderGet.from_domain_model(
163+
folder,
164+
folder.my_access_rights,
195165
)
196166
for folder in folders
197167
],
@@ -226,17 +196,9 @@ async def list_folders_full_depth(
226196
)
227197
return FolderGetPage(
228198
items=[
229-
FolderGet(
230-
folder_id=folder.folder_id,
231-
parent_folder_id=folder.parent_folder_id,
232-
name=folder.name,
233-
created_at=folder.created,
234-
modified_at=folder.modified,
235-
trashed_at=folder.trashed,
236-
trashed_by=folder.trashed_by,
237-
owner=folder.created_by_gid,
238-
workspace_id=folder.workspace_id,
239-
my_access_rights=folder.my_access_rights,
199+
FolderGet.from_domain_model(
200+
folder,
201+
folder.my_access_rights,
240202
)
241203
for folder in folders
242204
],
@@ -304,18 +266,7 @@ async def update_folder(
304266
parent_folder_id=parent_folder_id,
305267
product_name=product_name,
306268
)
307-
return FolderGet(
308-
folder_id=folder_db.folder_id,
309-
parent_folder_id=folder_db.parent_folder_id,
310-
name=folder_db.name,
311-
created_at=folder_db.created,
312-
modified_at=folder_db.modified,
313-
trashed_at=folder_db.trashed,
314-
trashed_by=folder_db.trashed_by,
315-
owner=folder_db.created_by_gid,
316-
workspace_id=folder_db.workspace_id,
317-
my_access_rights=user_folder_access_rights,
318-
)
269+
return FolderGet.from_domain_model(folder_db, user_folder_access_rights)
319270

320271

321272
async def delete_folder(

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

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
from ..db.plugin import get_asyncpg_engine
1313
from ..folders._trash_service import trash_folder, untrash_folder
1414
from ..projects._trash_service import trash_project, untrash_project
15-
from ._workspaces_repository import update_workspace
16-
from ._workspaces_service import check_user_workspace_access
15+
from . import _workspaces_repository, _workspaces_service
1716

1817
_logger = logging.getLogger(__name__)
1918

@@ -25,7 +24,7 @@ async def _check_exists_and_access(
2524
user_id: UserID,
2625
workspace_id: WorkspaceID,
2726
):
28-
await check_user_workspace_access(
27+
await _workspaces_service.check_user_workspace_access(
2928
app=app,
3029
user_id=user_id,
3130
workspace_id=workspace_id,
@@ -50,7 +49,7 @@ async def trash_workspace(
5049

5150
async with transaction_context(get_asyncpg_engine(app)) as connection:
5251
# EXPLICIT trash
53-
await update_workspace(
52+
await _workspaces_repository.update_workspace(
5453
app,
5554
connection,
5655
product_name=product_name,
@@ -59,10 +58,9 @@ async def trash_workspace(
5958
)
6059

6160
# IMPLICIT trash
62-
child_folders: list[FolderID] = (
63-
[]
61+
child_folders: list[FolderID] = [
6462
# NOTE: follows up with https://github.com/ITISFoundation/osparc-simcore/issues/7034
65-
)
63+
]
6664

6765
for folder_id in child_folders:
6866
await trash_folder(
@@ -73,10 +71,9 @@ async def trash_workspace(
7371
force_stop_first=force_stop_first,
7472
)
7573

76-
child_projects: list[ProjectID] = (
77-
[]
74+
child_projects: list[ProjectID] = [
7875
# NOTE: follows up with https://github.com/ITISFoundation/osparc-simcore/issues/7034
79-
)
76+
]
8077

8178
for project_id in child_projects:
8279
await trash_project(
@@ -102,18 +99,17 @@ async def untrash_workspace(
10299

103100
async with transaction_context(get_asyncpg_engine(app)) as connection:
104101
# EXPLICIT UNtrash
105-
await update_workspace(
102+
await _workspaces_repository.update_workspace(
106103
app,
107104
connection,
108105
product_name=product_name,
109106
workspace_id=workspace_id,
110107
updates=WorkspaceUpdates(trashed=None, trashed_by=None),
111108
)
112109

113-
child_folders: list[FolderID] = (
114-
[]
110+
child_folders: list[FolderID] = [
115111
# NOTE: follows up with https://github.com/ITISFoundation/osparc-simcore/issues/7034
116-
)
112+
]
117113

118114
for folder_id in child_folders:
119115
await untrash_folder(
@@ -123,10 +119,9 @@ async def untrash_workspace(
123119
folder_id=folder_id,
124120
)
125121

126-
child_projects: list[ProjectID] = (
127-
[]
122+
child_projects: list[ProjectID] = [
128123
# NOTE: follows up with https://github.com/ITISFoundation/osparc-simcore/issues/7034
129-
)
124+
]
130125

131126
for project_id in child_projects:
132127
await untrash_project(

services/web/server/tests/unit/with_dbs/03/test_trash.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,19 +448,19 @@ async def test_trash_folder_with_content(
448448
data, _ = await assert_status(resp, status.HTTP_200_OK)
449449
got = FolderGet.model_validate(data)
450450
assert got.trashed_at is not None
451-
assert got.trashed_by == logged_user["id"]
451+
assert got.trashed_by == logged_user["primary_gid"]
452452

453453
resp = await client.get(f"/v0/folders/{subfolder.folder_id}")
454454
data, _ = await assert_status(resp, status.HTTP_200_OK)
455455
got = FolderGet.model_validate(data)
456456
assert got.trashed_at is not None
457-
assert got.trashed_by == logged_user["id"]
457+
assert got.trashed_by == logged_user["primary_gid"]
458458

459459
resp = await client.get(f"/v0/projects/{project_uuid}")
460460
data, _ = await assert_status(resp, status.HTTP_200_OK)
461461
got = ProjectGet.model_validate(data)
462462
assert got.trashed_at is not None
463-
assert got.trashed_by == logged_user["id"]
463+
assert got.trashed_by == logged_user["primary_gid"]
464464

465465
# UNTRASH folder
466466
resp = await client.post(f"/v0/folders/{folder.folder_id}:untrash")

0 commit comments

Comments
 (0)