Skip to content

Commit 9a1b399

Browse files
committed
projects trashed by primary gid
1 parent c3f8bd7 commit 9a1b399

File tree

6 files changed

+50
-60
lines changed

6 files changed

+50
-60
lines changed

services/web/server/src/simcore_service_webserver/projects/_db_utils.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
from simcore_postgres_database.models.projects_to_products import projects_to_products
2020
from simcore_postgres_database.webserver_models import ProjectType, projects
2121
from sqlalchemy.dialects.postgresql import insert as pg_insert
22-
from sqlalchemy.sql import select
2322
from sqlalchemy.sql.selectable import CompoundSelect, Select
2423

2524
from ..db.models import GroupType, groups, projects_tags, user_to_groups, users
2625
from ..users.exceptions import UserNotFoundError
2726
from ..utils import format_datetime
27+
from ._projects_db import BASE_PROJECT_SELECT_ARGS
2828
from .exceptions import (
2929
NodeNotFoundError,
3030
ProjectInvalidRightsError,
@@ -130,7 +130,7 @@ async def _list_user_groups(
130130
user_groups.append(everyone_group)
131131
else:
132132
result = await conn.execute(
133-
select(groups)
133+
sa.select(groups)
134134
.select_from(groups.join(user_to_groups))
135135
.where(user_to_groups.c.uid == user_id)
136136
)
@@ -255,7 +255,7 @@ async def _get_project(
255255
exclude_foreign = exclude_foreign or []
256256

257257
access_rights_subquery = (
258-
select(
258+
sa.select(
259259
project_to_groups.c.project_uuid,
260260
sa.func.jsonb_object_agg(
261261
project_to_groups.c.gid,
@@ -275,29 +275,14 @@ async def _get_project(
275275

276276
query = (
277277
sa.select(
278-
projects.c.id,
279-
projects.c.type,
280-
projects.c.uuid,
281-
projects.c.name,
282-
projects.c.description,
283-
projects.c.thumbnail,
284-
projects.c.prj_owner, # == user.id (who created)
285-
projects.c.creation_date,
286-
projects.c.last_change_date,
287-
projects.c.workbench,
288-
projects.c.ui,
289-
projects.c.classifiers,
290-
projects.c.dev,
291-
projects.c.quality,
292-
projects.c.published,
293-
projects.c.hidden,
294-
projects.c.trashed,
295-
projects.c.trashed_by, # == user.id (who trashed)
296-
projects.c.trashed_explicitly,
297-
projects.c.workspace_id,
278+
*BASE_PROJECT_SELECT_ARGS,
298279
access_rights_subquery.c.access_rights,
299280
)
300-
.select_from(projects.join(access_rights_subquery, isouter=True))
281+
.select_from(
282+
projects.join(access_rights_subquery, isouter=True).outerjoin(
283+
users, projects.c.trashed_by == users.c.id
284+
)
285+
)
301286
.where(
302287
(projects.c.uuid == f"{project_uuid}")
303288
& (

services/web/server/src/simcore_service_webserver/projects/_projects_db.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import sqlalchemy as sa
44
from aiohttp import web
55
from models_library.projects import ProjectID
6+
from simcore_postgres_database.models.projects import projects
7+
from simcore_postgres_database.models.users import users
68
from simcore_postgres_database.utils_repos import transaction_context
7-
from simcore_postgres_database.webserver_models import projects
89
from sqlalchemy.ext.asyncio import AsyncConnection
910

1011
from ..db.plugin import get_asyncpg_engine
@@ -14,17 +15,17 @@
1415
_logger = logging.getLogger(__name__)
1516

1617

17-
# NOTE: MD: I intentionally didn't include the workbench. There is a special interface
18-
# for the workbench, and at some point, this column should be removed from the table.
19-
# The same holds true for access_rights/ui/classifiers/quality, but we have decided to proceed step by step.
20-
_SELECTION_PROJECT_DB_ARGS = [ # noqa: RUF012
18+
PROJECT_WITHOUT_WORKBENCH_COLS = [ # noqa: RUF012
19+
# NOTE: MD: I intentionally didn't include the workbench. There is a special interface
20+
# for the workbench, and at some point, this column should be removed from the table.
21+
# The same holds true for access_rights/ui/classifiers/quality, but we have decided to proceed step by step.
2122
projects.c.id,
2223
projects.c.type,
2324
projects.c.uuid,
2425
projects.c.name,
2526
projects.c.description,
2627
projects.c.thumbnail,
27-
projects.c.prj_owner,
28+
projects.c.prj_owner, # == user.id (who created)
2829
projects.c.creation_date,
2930
projects.c.last_change_date,
3031
projects.c.ui,
@@ -35,10 +36,17 @@
3536
projects.c.hidden,
3637
projects.c.workspace_id,
3738
projects.c.trashed,
38-
projects.c.trashed_by,
39+
projects.c.trashed_by, # == user.id (who trashed)
3940
projects.c.trashed_explicitly,
4041
]
4142

43+
BASE_PROJECT_SELECT_ARGS = [
44+
*PROJECT_WITHOUT_WORKBENCH_COLS,
45+
projects.c.workbench,
46+
users.c.primary_gid.label("trashed_by_primary_gid"),
47+
# NOTE: needs `.outerjoin(users, projects.c.trashed_by == users.c.id)`
48+
]
49+
4250

4351
async def patch_project(
4452
app: web.Application,
@@ -53,7 +61,7 @@ async def patch_project(
5361
projects.update()
5462
.values(last_change_date=sa.func.now(), **new_partial_project_data)
5563
.where(projects.c.uuid == f"{project_uuid}")
56-
.returning(*_SELECTION_PROJECT_DB_ARGS)
64+
.returning(*PROJECT_WITHOUT_WORKBENCH_COLS)
5765
)
5866
row = await result.first()
5967
if row is None:

services/web/server/src/simcore_service_webserver/projects/db.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
patch_workbench,
8686
update_workbench,
8787
)
88-
from ._projects_db import _SELECTION_PROJECT_DB_ARGS
88+
from ._projects_db import BASE_PROJECT_SELECT_ARGS, PROJECT_WITHOUT_WORKBENCH_COLS
8989
from .exceptions import (
9090
ProjectDeleteError,
9191
ProjectInvalidRightsError,
@@ -427,11 +427,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
427427

428428
private_workspace_query = (
429429
sa.select(
430-
*[
431-
col
432-
for col in projects.columns
433-
if col.name not in ["access_rights"]
434-
],
430+
*BASE_PROJECT_SELECT_ARGS,
435431
self.access_rights_subquery.c.access_rights,
436432
projects_to_products.c.product_name,
437433
projects_to_folders.c.folder_id,
@@ -452,6 +448,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
452448
isouter=True,
453449
)
454450
.join(project_tags_subquery, isouter=True)
451+
.outerjoin(users, projects.c.trashed_by == users.c.id)
455452
)
456453
.where(
457454
(
@@ -484,11 +481,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
484481

485482
shared_workspace_query = (
486483
sa.select(
487-
*[
488-
col
489-
for col in projects.columns
490-
if col.name not in ["access_rights"]
491-
],
484+
*BASE_PROJECT_SELECT_ARGS,
492485
workspace_access_rights_subquery.c.access_rights,
493486
projects_to_products.c.product_name,
494487
projects_to_folders.c.folder_id,
@@ -513,6 +506,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
513506
isouter=True,
514507
)
515508
.join(project_tags_subquery, isouter=True)
509+
.outerjoin(users, projects.c.trashed_by == users.c.id)
516510
)
517511
.where(
518512
(
@@ -685,9 +679,13 @@ async def get_project(
685679
async def get_project_db(self, project_uuid: ProjectID) -> ProjectDB:
686680
async with self.engine.acquire() as conn:
687681
result = await conn.execute(
688-
sa.select(*_SELECTION_PROJECT_DB_ARGS).where(
689-
projects.c.uuid == f"{project_uuid}"
682+
sa.select(
683+
*BASE_PROJECT_SELECT_ARGS,
684+
)
685+
.select_from(
686+
projects.outerjoin(users, projects.c.trashed_by == users.c.id)
690687
)
688+
.where(projects.c.uuid == f"{project_uuid}")
691689
)
692690
row = await result.fetchone()
693691
if row is None:
@@ -699,7 +697,10 @@ async def get_user_specific_project_data_db(
699697
) -> UserSpecificProjectDataDB:
700698
async with self.engine.acquire() as conn:
701699
result = await conn.execute(
702-
sa.select(*_SELECTION_PROJECT_DB_ARGS, projects_to_folders.c.folder_id)
700+
sa.select(
701+
*PROJECT_WITHOUT_WORKBENCH_COLS,
702+
projects_to_folders.c.folder_id,
703+
)
703704
.select_from(
704705
projects.join(
705706
projects_to_folders,

services/web/server/src/simcore_service_webserver/projects/models.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from common_library.dict_tools import remap_keys
77
from models_library.api_schemas_webserver.projects import ProjectPatch
88
from models_library.folders import FolderID
9+
from models_library.groups import GroupID
910
from models_library.projects import ClassifierID, ProjectID
1011
from models_library.projects_ui import StudyUI
1112
from models_library.users import UserID
@@ -15,7 +16,7 @@
1516
)
1617
from models_library.workspaces import WorkspaceID
1718
from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator
18-
from simcore_postgres_database.models.projects import ProjectType, projects
19+
from simcore_postgres_database.models.projects import ProjectType
1920

2021
ProjectDict: TypeAlias = dict[str, Any]
2122
ProjectProxy: TypeAlias = RowProxy
@@ -54,6 +55,7 @@ class ProjectDB(BaseModel):
5455
workspace_id: WorkspaceID | None
5556
trashed: datetime | None
5657
trashed_by: UserID | None
58+
trashed_by_primary_gid: GroupID | None = None
5759
trashed_explicitly: bool = False
5860

5961
model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)
@@ -73,11 +75,6 @@ class UserSpecificProjectDataDB(ProjectDB):
7375
model_config = ConfigDict(from_attributes=True)
7476

7577

76-
assert set(ProjectDB.model_fields.keys()).issubset( # nosec
77-
{c.name for c in projects.columns if c.name not in ["access_rights"]}
78-
)
79-
80-
8178
class UserProjectAccessRightsDB(BaseModel):
8279
uid: UserID
8380
read: bool

services/web/server/src/simcore_service_webserver/projects/projects_api.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,13 @@
121121
from ..wallets import api as wallets_api
122122
from ..wallets.errors import WalletNotEnoughCreditsError
123123
from ..workspaces import _workspaces_repository as workspaces_db
124-
from . import _crud_api_delete, _nodes_api, _projects_db
124+
from . import _crud_api_delete, _nodes_api, _projects_db, _wallets_api
125125
from ._access_rights_api import (
126126
check_user_project_permission,
127127
has_user_project_access_rights,
128128
)
129129
from ._db_utils import PermissionStr
130130
from ._nodes_utils import set_reservation_same_as_limit, validate_new_service_resources
131-
from ._wallets_api import connect_wallet_to_project, get_project_wallet
132131
from .db import APP_PROJECT_DBAPI, ProjectDBAPI
133132
from .exceptions import (
134133
ClustersKeeperNotAvailableError,
@@ -623,7 +622,7 @@ async def _() -> None:
623622
and app_settings.WEBSERVER_CREDIT_COMPUTATION_ENABLED
624623
):
625624
# Deal with Wallet
626-
project_wallet = await get_project_wallet(
625+
project_wallet = await _wallets_api.get_project_wallet(
627626
request.app, project_id=project_uuid
628627
)
629628
if project_wallet is None:
@@ -638,7 +637,7 @@ async def _() -> None:
638637
project_wallet_id = TypeAdapter(WalletID).validate_python(
639638
user_default_wallet_preference.value
640639
)
641-
await connect_wallet_to_project(
640+
await _wallets_api.connect_wallet_to_project(
642641
request.app,
643642
product_name=product_name,
644643
project_id=project_uuid,

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ async def test_trash_projects( # noqa: PLR0915
151151
assert got.trashed_at
152152
assert trashing_at < got.trashed_at
153153
assert got.trashed_at < arrow.utcnow().datetime
154-
assert got.trashed_by == logged_user["id"]
154+
assert got.trashed_by == logged_user["primary_gid"]
155155

156156
# LIST trashed
157157
resp = await client.get("/v0/projects", params={"filters": '{"trashed": true}'})
@@ -251,7 +251,7 @@ async def test_trash_projects_shared_among_users(
251251
assert page.data[0].uuid == project_uuid
252252
assert page.data[0].trashed_at
253253
assert trashing_at < page.data[0].trashed_at
254-
assert page.data[0].trashed_by == logged_user["id"]
254+
assert page.data[0].trashed_by == logged_user["primary_gid"]
255255

256256
# Swith USER: LOGOUT
257257
url = client.app.router["auth_logout"].url_for()
@@ -277,7 +277,7 @@ async def test_trash_projects_shared_among_users(
277277
assert page.data[0].uuid == project_uuid
278278
assert page.data[0].trashed_at
279279
assert trashing_at < page.data[0].trashed_at
280-
assert page.data[0].trashed_by == logged_user["id"]
280+
assert page.data[0].trashed_by == logged_user["primary_gid"]
281281

282282

283283
@pytest.mark.acceptance_test(
@@ -460,7 +460,7 @@ async def test_trash_folder_with_content(
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)