Skip to content

Commit 26d3a46

Browse files
committed
adapts folders
1 parent 48d358c commit 26d3a46

File tree

9 files changed

+181
-88
lines changed

9 files changed

+181
-88
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,23 @@ class FolderGet(OutputSchema):
2929

3030
@classmethod
3131
def from_domain_model(
32-
cls, folder_db: FolderDB, user_folder_access_rights: AccessRights
32+
cls,
33+
folder_db: FolderDB,
34+
trashed_by_primary_gid: GroupID | None,
35+
user_folder_access_rights: AccessRights,
3336
) -> Self:
34-
return cls.model_construct(
37+
if (folder_db.trashed_by is None) ^ (trashed_by_primary_gid is None):
38+
msg = f"Incompatible inputs: {folder_db.trashed_by=} but not {trashed_by_primary_gid=}"
39+
raise ValueError(msg)
40+
41+
return cls(
3542
folder_id=folder_db.folder_id,
3643
parent_folder_id=folder_db.parent_folder_id,
3744
name=folder_db.name,
3845
created_at=folder_db.created,
3946
modified_at=folder_db.modified,
4047
trashed_at=folder_db.trashed,
41-
trashed_by=folder_db.trashed_by_primary_gid,
48+
trashed_by=trashed_by_primary_gid,
4249
owner=folder_db.created_by_gid,
4350
workspace_id=folder_db.workspace_id,
4451
my_access_rights=user_folder_access_rights,

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ def validate_folder_id(cls, value, info: ValidationInfo):
3636
return value
3737

3838

39-
#
40-
# DB
41-
#
42-
43-
4439
class FolderDB(BaseModel):
4540
folder_id: FolderID
4641
name: str
@@ -52,20 +47,20 @@ class FolderDB(BaseModel):
5247

5348
trashed: datetime | None
5449
trashed_by: UserID | None
55-
trashed_by_primary_gid: GroupID | None = None
5650
trashed_explicitly: bool
5751

5852
user_id: UserID | None # owner?
5953
workspace_id: WorkspaceID | None
6054
model_config = ConfigDict(from_attributes=True)
6155

6256

63-
class UserFolderAccessRightsDB(FolderDB):
57+
class UserFolder(FolderDB):
6458
my_access_rights: AccessRights
6559

6660
model_config = ConfigDict(from_attributes=True)
6761

6862

69-
class Folder(NamedTuple):
63+
class FolderTuple(NamedTuple):
7064
folder_db: FolderDB
65+
trashed_by_primary_gid: GroupID | None
7166
my_access_rights: AccessRights

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

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
FolderID,
1717
FolderQuery,
1818
FolderScope,
19-
UserFolderAccessRightsDB,
19+
UserFolder,
2020
)
2121
from models_library.groups import GroupID
2222
from models_library.products import ProductName
@@ -30,6 +30,7 @@
3030
from simcore_postgres_database.models.projects_to_folders import projects_to_folders
3131
from simcore_postgres_database.models.users import users
3232
from simcore_postgres_database.utils_repos import (
33+
get_columns_from_db_model,
3334
pass_or_acquire_connection,
3435
transaction_context,
3536
)
@@ -49,19 +50,7 @@
4950
_unset: Final = UnSet()
5051

5152

52-
_FOLDERS_SELECTION_COLS = (
53-
folders_v2.c.folder_id,
54-
folders_v2.c.name,
55-
folders_v2.c.parent_folder_id,
56-
folders_v2.c.created_by_gid,
57-
folders_v2.c.created,
58-
folders_v2.c.modified,
59-
folders_v2.c.trashed,
60-
folders_v2.c.trashed_by,
61-
folders_v2.c.trashed_explicitly,
62-
folders_v2.c.user_id,
63-
folders_v2.c.workspace_id,
64-
)
53+
_FOLDER_DB_MODEL_COLS = get_columns_from_db_model(folders_v2, FolderDB)
6554

6655

6756
async def create(
@@ -92,7 +81,7 @@ async def create(
9281
created=func.now(),
9382
modified=func.now(),
9483
)
95-
.returning(*_FOLDERS_SELECTION_COLS)
84+
.returning(*_FOLDER_DB_MODEL_COLS)
9685
)
9786
row = await result.first()
9887
return FolderDB.model_validate(row)
@@ -110,7 +99,7 @@ def _create_private_workspace_query(
11099
)
111100
return (
112101
select(
113-
*_FOLDERS_SELECTION_COLS,
102+
*_FOLDER_DB_MODEL_COLS,
114103
func.json_build_object(
115104
"read",
116105
sa.text("true"),
@@ -147,7 +136,7 @@ def _create_shared_workspace_query(
147136

148137
shared_workspace_query = (
149138
select(
150-
*_FOLDERS_SELECTION_COLS,
139+
*_FOLDER_DB_MODEL_COLS,
151140
workspace_access_rights_subquery.c.my_access_rights,
152141
)
153142
.select_from(
@@ -191,7 +180,7 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
191180
limit: int,
192181
# order
193182
order_by: OrderBy,
194-
) -> tuple[int, list[UserFolderAccessRightsDB]]:
183+
) -> tuple[int, list[UserFolder]]:
195184
"""
196185
folder_query - Used to filter in which folder we want to list folders.
197186
trashed - If set to true, it returns folders **explicitly** trashed, if false then non-trashed folders.
@@ -266,23 +255,16 @@ async def list_( # pylint: disable=too-many-arguments,too-many-branches
266255
total_count = await conn.scalar(count_query)
267256

268257
result = await conn.stream(list_query)
269-
folders: list[UserFolderAccessRightsDB] = [
270-
UserFolderAccessRightsDB.model_validate(row) async for row in result
258+
folders: list[UserFolder] = [
259+
UserFolder.model_validate(row) async for row in result
271260
]
272261
return cast(int, total_count), folders
273262

274263

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-
)
264+
def _create_base_select_query(folder_id: FolderID, product_name: ProductName) -> Select:
265+
return select(*_FOLDER_DB_MODEL_COLS,).where(
266+
(folders_v2.c.product_name == product_name)
267+
& (folders_v2.c.folder_id == folder_id)
286268
)
287269

288270

@@ -369,7 +351,7 @@ async def update(
369351
query = (
370352
(folders_v2.update().values(modified=func.now(), **updated))
371353
.where(folders_v2.c.product_name == product_name)
372-
.returning(*_FOLDERS_SELECTION_COLS)
354+
.returning(*_FOLDER_DB_MODEL_COLS)
373355
)
374356

375357
if isinstance(folders_id_or_ids, set):
@@ -587,3 +569,48 @@ async def get_folders_recursively(
587569
final_query = select(folder_hierarchy_cte)
588570
result = await conn.stream(final_query)
589571
return cast(list[FolderID], [row.folder_id async for row in result])
572+
573+
574+
async def get_trashed_by_primary_gid(
575+
app: web.Application,
576+
connection: AsyncConnection | None = None,
577+
*,
578+
folder_id: FolderID,
579+
) -> GroupID | None:
580+
query = (
581+
sa.select(
582+
users.c.primary_gid.label("trashed_by_primary_gid"),
583+
)
584+
.select_from(projects.outerjoin(users, projects.c.trashed_by == users.c.id))
585+
.where(folders_v2.c.folder_id == folder_id)
586+
)
587+
588+
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
589+
result = await conn.execute(query)
590+
row = result.first()
591+
return row.trashed_by_primary_gid if row else None
592+
593+
594+
async def batch_get_trashed_by_primary_gid(
595+
app: web.Application,
596+
connection: AsyncConnection | None = None,
597+
*,
598+
folders_ids: list[FolderID],
599+
) -> list[GroupID | None]:
600+
601+
query = (
602+
sa.select(
603+
users.c.primary_gid.label("trashed_by_primary_gid"),
604+
)
605+
.select_from(folders_v2.outerjoin(users, folders_v2.c.trashed_by == users.c.id))
606+
.where(folders_v2.c.folder_id.in_(folders_ids))
607+
).order_by(
608+
*[
609+
folders_v2.c.folder_id == _id for _id in folders_ids
610+
] # Preserves the order of folders_ids
611+
)
612+
613+
async with pass_or_acquire_connection(get_asyncpg_engine(app), connection) as conn:
614+
result = await conn.execute(query)
615+
rows = result.fetchall()
616+
return [row.trashed_by_primary_gid for row in rows]

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
FolderGet,
77
FolderReplaceBodyParams,
88
)
9-
from models_library.folders import Folder
9+
from models_library.folders import FolderTuple
1010
from models_library.rest_ordering import OrderBy
1111
from models_library.rest_pagination import ItemT, Page
1212
from models_library.rest_pagination_utils import paginate_data
@@ -54,7 +54,7 @@ async def create_folder(request: web.Request):
5454
req_ctx = FoldersRequestContext.model_validate(request)
5555
body_params = await parse_request_body_as(FolderCreateBodyParams, request)
5656

57-
folder: Folder = await _folders_service.create_folder(
57+
folder: FolderTuple = await _folders_service.create_folder(
5858
request.app,
5959
user_id=req_ctx.user_id,
6060
name=body_params.name,
@@ -64,7 +64,11 @@ async def create_folder(request: web.Request):
6464
)
6565

6666
return envelope_json_response(
67-
FolderGet.from_domain_model(folder.folder_db, folder.my_access_rights),
67+
FolderGet.from_domain_model(
68+
folder.folder_db,
69+
trashed_by_primary_gid=None,
70+
user_folder_access_rights=folder.my_access_rights,
71+
),
6872
web.HTTPCreated,
6973
)
7074

@@ -150,14 +154,18 @@ async def get_folder(request: web.Request):
150154
req_ctx = FoldersRequestContext.model_validate(request)
151155
path_params = parse_request_path_parameters_as(FoldersPathParams, request)
152156

153-
folder: Folder = await _folders_service.get_folder(
157+
folder: FolderTuple = await _folders_service.get_folder(
154158
app=request.app,
155159
folder_id=path_params.folder_id,
156160
user_id=req_ctx.user_id,
157161
product_name=req_ctx.product_name,
158162
)
159163
return envelope_json_response(
160-
FolderGet.from_domain_model(folder.folder_db, folder.my_access_rights)
164+
FolderGet.from_domain_model(
165+
folder.folder_db,
166+
folder.trashed_by_primary_gid,
167+
user_folder_access_rights=folder.my_access_rights,
168+
)
161169
)
162170

163171

@@ -173,7 +181,7 @@ async def replace_folder(request: web.Request):
173181
path_params = parse_request_path_parameters_as(FoldersPathParams, request)
174182
body_params = await parse_request_body_as(FolderReplaceBodyParams, request)
175183

176-
folder: Folder = await _folders_service.update_folder(
184+
folder: FolderTuple = await _folders_service.update_folder(
177185
app=request.app,
178186
user_id=req_ctx.user_id,
179187
folder_id=path_params.folder_id,
@@ -182,7 +190,11 @@ async def replace_folder(request: web.Request):
182190
product_name=req_ctx.product_name,
183191
)
184192
return envelope_json_response(
185-
FolderGet.from_domain_model(folder.folder_db, folder.my_access_rights)
193+
FolderGet.from_domain_model(
194+
folder.folder_db,
195+
folder.trashed_by_primary_gid,
196+
user_folder_access_rights=folder.my_access_rights,
197+
)
186198
)
187199

188200

0 commit comments

Comments
 (0)