Skip to content

Commit

Permalink
projects trashed by primary gid
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov committed Jan 17, 2025
1 parent c3f8bd7 commit 9a1b399
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
from simcore_postgres_database.models.projects_to_products import projects_to_products
from simcore_postgres_database.webserver_models import ProjectType, projects
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.sql import select
from sqlalchemy.sql.selectable import CompoundSelect, Select

from ..db.models import GroupType, groups, projects_tags, user_to_groups, users
from ..users.exceptions import UserNotFoundError
from ..utils import format_datetime
from ._projects_db import BASE_PROJECT_SELECT_ARGS
from .exceptions import (
NodeNotFoundError,
ProjectInvalidRightsError,
Expand Down Expand Up @@ -130,7 +130,7 @@ async def _list_user_groups(
user_groups.append(everyone_group)
else:
result = await conn.execute(
select(groups)
sa.select(groups)
.select_from(groups.join(user_to_groups))
.where(user_to_groups.c.uid == user_id)
)
Expand Down Expand Up @@ -255,7 +255,7 @@ async def _get_project(
exclude_foreign = exclude_foreign or []

access_rights_subquery = (
select(
sa.select(
project_to_groups.c.project_uuid,
sa.func.jsonb_object_agg(
project_to_groups.c.gid,
Expand All @@ -275,29 +275,14 @@ async def _get_project(

query = (
sa.select(
projects.c.id,
projects.c.type,
projects.c.uuid,
projects.c.name,
projects.c.description,
projects.c.thumbnail,
projects.c.prj_owner, # == user.id (who created)
projects.c.creation_date,
projects.c.last_change_date,
projects.c.workbench,
projects.c.ui,
projects.c.classifiers,
projects.c.dev,
projects.c.quality,
projects.c.published,
projects.c.hidden,
projects.c.trashed,
projects.c.trashed_by, # == user.id (who trashed)
projects.c.trashed_explicitly,
projects.c.workspace_id,
*BASE_PROJECT_SELECT_ARGS,
access_rights_subquery.c.access_rights,
)
.select_from(projects.join(access_rights_subquery, isouter=True))
.select_from(
projects.join(access_rights_subquery, isouter=True).outerjoin(
users, projects.c.trashed_by == users.c.id
)
)
.where(
(projects.c.uuid == f"{project_uuid}")
& (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import sqlalchemy as sa
from aiohttp import web
from models_library.projects import ProjectID
from simcore_postgres_database.models.projects import projects
from simcore_postgres_database.models.users import users
from simcore_postgres_database.utils_repos import transaction_context
from simcore_postgres_database.webserver_models import projects
from sqlalchemy.ext.asyncio import AsyncConnection

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


# NOTE: MD: I intentionally didn't include the workbench. There is a special interface
# for the workbench, and at some point, this column should be removed from the table.
# The same holds true for access_rights/ui/classifiers/quality, but we have decided to proceed step by step.
_SELECTION_PROJECT_DB_ARGS = [ # noqa: RUF012
PROJECT_WITHOUT_WORKBENCH_COLS = [ # noqa: RUF012
# NOTE: MD: I intentionally didn't include the workbench. There is a special interface
# for the workbench, and at some point, this column should be removed from the table.
# The same holds true for access_rights/ui/classifiers/quality, but we have decided to proceed step by step.
projects.c.id,
projects.c.type,
projects.c.uuid,
projects.c.name,
projects.c.description,
projects.c.thumbnail,
projects.c.prj_owner,
projects.c.prj_owner, # == user.id (who created)
projects.c.creation_date,
projects.c.last_change_date,
projects.c.ui,
Expand All @@ -35,10 +36,17 @@
projects.c.hidden,
projects.c.workspace_id,
projects.c.trashed,
projects.c.trashed_by,
projects.c.trashed_by, # == user.id (who trashed)
projects.c.trashed_explicitly,
]

BASE_PROJECT_SELECT_ARGS = [
*PROJECT_WITHOUT_WORKBENCH_COLS,
projects.c.workbench,
users.c.primary_gid.label("trashed_by_primary_gid"),
# NOTE: needs `.outerjoin(users, projects.c.trashed_by == users.c.id)`
]


async def patch_project(
app: web.Application,
Expand All @@ -53,7 +61,7 @@ async def patch_project(
projects.update()
.values(last_change_date=sa.func.now(), **new_partial_project_data)
.where(projects.c.uuid == f"{project_uuid}")
.returning(*_SELECTION_PROJECT_DB_ARGS)
.returning(*PROJECT_WITHOUT_WORKBENCH_COLS)
)
row = await result.first()
if row is None:
Expand Down
29 changes: 15 additions & 14 deletions services/web/server/src/simcore_service_webserver/projects/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
patch_workbench,
update_workbench,
)
from ._projects_db import _SELECTION_PROJECT_DB_ARGS
from ._projects_db import BASE_PROJECT_SELECT_ARGS, PROJECT_WITHOUT_WORKBENCH_COLS
from .exceptions import (
ProjectDeleteError,
ProjectInvalidRightsError,
Expand Down Expand Up @@ -427,11 +427,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen

private_workspace_query = (
sa.select(
*[
col
for col in projects.columns
if col.name not in ["access_rights"]
],
*BASE_PROJECT_SELECT_ARGS,
self.access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
Expand All @@ -452,6 +448,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
isouter=True,
)
.join(project_tags_subquery, isouter=True)
.outerjoin(users, projects.c.trashed_by == users.c.id)
)
.where(
(
Expand Down Expand Up @@ -484,11 +481,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen

shared_workspace_query = (
sa.select(
*[
col
for col in projects.columns
if col.name not in ["access_rights"]
],
*BASE_PROJECT_SELECT_ARGS,
workspace_access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
Expand All @@ -513,6 +506,7 @@ async def list_projects( # pylint: disable=too-many-arguments,too-many-statemen
isouter=True,
)
.join(project_tags_subquery, isouter=True)
.outerjoin(users, projects.c.trashed_by == users.c.id)
)
.where(
(
Expand Down Expand Up @@ -685,9 +679,13 @@ async def get_project(
async def get_project_db(self, project_uuid: ProjectID) -> ProjectDB:
async with self.engine.acquire() as conn:
result = await conn.execute(
sa.select(*_SELECTION_PROJECT_DB_ARGS).where(
projects.c.uuid == f"{project_uuid}"
sa.select(
*BASE_PROJECT_SELECT_ARGS,
)
.select_from(
projects.outerjoin(users, projects.c.trashed_by == users.c.id)
)
.where(projects.c.uuid == f"{project_uuid}")
)
row = await result.fetchone()
if row is None:
Expand All @@ -699,7 +697,10 @@ async def get_user_specific_project_data_db(
) -> UserSpecificProjectDataDB:
async with self.engine.acquire() as conn:
result = await conn.execute(
sa.select(*_SELECTION_PROJECT_DB_ARGS, projects_to_folders.c.folder_id)
sa.select(
*PROJECT_WITHOUT_WORKBENCH_COLS,
projects_to_folders.c.folder_id,
)
.select_from(
projects.join(
projects_to_folders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from common_library.dict_tools import remap_keys
from models_library.api_schemas_webserver.projects import ProjectPatch
from models_library.folders import FolderID
from models_library.groups import GroupID
from models_library.projects import ClassifierID, ProjectID
from models_library.projects_ui import StudyUI
from models_library.users import UserID
Expand All @@ -15,7 +16,7 @@
)
from models_library.workspaces import WorkspaceID
from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator
from simcore_postgres_database.models.projects import ProjectType, projects
from simcore_postgres_database.models.projects import ProjectType

ProjectDict: TypeAlias = dict[str, Any]
ProjectProxy: TypeAlias = RowProxy
Expand Down Expand Up @@ -54,6 +55,7 @@ class ProjectDB(BaseModel):
workspace_id: WorkspaceID | None
trashed: datetime | None
trashed_by: UserID | None
trashed_by_primary_gid: GroupID | None = None
trashed_explicitly: bool = False

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


assert set(ProjectDB.model_fields.keys()).issubset( # nosec
{c.name for c in projects.columns if c.name not in ["access_rights"]}
)


class UserProjectAccessRightsDB(BaseModel):
uid: UserID
read: bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,13 @@
from ..wallets import api as wallets_api
from ..wallets.errors import WalletNotEnoughCreditsError
from ..workspaces import _workspaces_repository as workspaces_db
from . import _crud_api_delete, _nodes_api, _projects_db
from . import _crud_api_delete, _nodes_api, _projects_db, _wallets_api
from ._access_rights_api import (
check_user_project_permission,
has_user_project_access_rights,
)
from ._db_utils import PermissionStr
from ._nodes_utils import set_reservation_same_as_limit, validate_new_service_resources
from ._wallets_api import connect_wallet_to_project, get_project_wallet
from .db import APP_PROJECT_DBAPI, ProjectDBAPI
from .exceptions import (
ClustersKeeperNotAvailableError,
Expand Down Expand Up @@ -623,7 +622,7 @@ async def _() -> None:
and app_settings.WEBSERVER_CREDIT_COMPUTATION_ENABLED
):
# Deal with Wallet
project_wallet = await get_project_wallet(
project_wallet = await _wallets_api.get_project_wallet(
request.app, project_id=project_uuid
)
if project_wallet is None:
Expand All @@ -638,7 +637,7 @@ async def _() -> None:
project_wallet_id = TypeAdapter(WalletID).validate_python(
user_default_wallet_preference.value
)
await connect_wallet_to_project(
await _wallets_api.connect_wallet_to_project(
request.app,
product_name=product_name,
project_id=project_uuid,
Expand Down
8 changes: 4 additions & 4 deletions services/web/server/tests/unit/with_dbs/03/test_trash.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ async def test_trash_projects( # noqa: PLR0915
assert got.trashed_at
assert trashing_at < got.trashed_at
assert got.trashed_at < arrow.utcnow().datetime
assert got.trashed_by == logged_user["id"]
assert got.trashed_by == logged_user["primary_gid"]

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

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


@pytest.mark.acceptance_test(
Expand Down Expand Up @@ -460,7 +460,7 @@ async def test_trash_folder_with_content(
data, _ = await assert_status(resp, status.HTTP_200_OK)
got = ProjectGet.model_validate(data)
assert got.trashed_at is not None
assert got.trashed_by == logged_user["id"]
assert got.trashed_by == logged_user["primary_gid"]

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

0 comments on commit 9a1b399

Please sign in to comment.