Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ web-api: trashed resources include trashedBy with the primary GID of the user that trashed it #7052

Merged
merged 44 commits into from
Jan 27, 2025
Merged
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4d0245c
drafts tests
pcrespov Jan 15, 2025
41b94c4
back to uid
pcrespov Jan 15, 2025
47d5255
rm import
pcrespov Jan 16, 2025
10b5e79
fixes userid
pcrespov Jan 16, 2025
a900b67
fixes folders
pcrespov Jan 16, 2025
5e2e63c
doc
pcrespov Jan 17, 2025
b3f671b
updates OAS
pcrespov Jan 17, 2025
9384eef
services/webserver api version: 0.50.0 → 0.51.0
pcrespov Jan 17, 2025
f5357ad
updates workspaces to return groupid
pcrespov Jan 17, 2025
607c6a3
updates fakes in api-server
pcrespov Jan 17, 2025
a51dedf
fixes tests
pcrespov Jan 17, 2025
9582a27
folders trashed-by
pcrespov Jan 17, 2025
908e453
domain models in folders
pcrespov Jan 17, 2025
4abded4
doc
pcrespov Jan 17, 2025
93dbfda
fixes tests
pcrespov Jan 17, 2025
d9c57cd
projects schema, domain and data models
pcrespov Jan 17, 2025
92951c0
projects trashed by primary gid
pcrespov Jan 17, 2025
2f574a5
fix tests
pcrespov Jan 17, 2025
fa42231
fix tests
pcrespov Jan 17, 2025
afbbc44
fix mypy
pcrespov Jan 18, 2025
c069123
fix bad merge
pcrespov Jan 22, 2025
a77e643
split ProjectDB
pcrespov Jan 22, 2025
a0d8de7
read functions returning ProjectDict
pcrespov Jan 22, 2025
007a1e6
list of project dict gets trashed_by_primary_gid
pcrespov Jan 22, 2025
4d438e0
list of project dict gets trashed_by_primary_gid
pcrespov Jan 22, 2025
d5016e3
doc
pcrespov Jan 22, 2025
5905876
handlers return item
pcrespov Jan 22, 2025
ced0e98
by_alias since Page is strict
pcrespov Jan 22, 2025
b3cc85e
fixes test_proejcts_cancellations
pcrespov Jan 22, 2025
4e069ef
fixes tests
pcrespov Jan 22, 2025
294dc0b
adapts folders
pcrespov Jan 22, 2025
667daa8
testing folders
pcrespov Jan 22, 2025
f4c6886
minor
pcrespov Jan 23, 2025
23fca69
fixes test and refactor query
pcrespov Jan 23, 2025
1bf2942
fixes order
pcrespov Jan 23, 2025
347d565
@GitHK review: annotations
pcrespov Jan 23, 2025
8f0a626
@bisgaard-itis review: camelcase or not
pcrespov Jan 23, 2025
906a875
refactor to reduce complexity (sonarcloud)
pcrespov Jan 23, 2025
09644ab
fixes cancelation errors
pcrespov Jan 23, 2025
5b8a385
cleanup
pcrespov Jan 23, 2025
fadfb3a
sonarcloud complexity and pylint
pcrespov Jan 23, 2025
7a92b48
drop
pcrespov Jan 23, 2025
0a7b242
fixes tests
pcrespov Jan 24, 2025
1340862
fixes bug introduced in workspaces
pcrespov Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor to reduce complexity (sonarcloud)
pcrespov committed Jan 27, 2025
commit 906a8755c6e0ec003707b03fa7d6503dc24ce76b
307 changes: 169 additions & 138 deletions services/web/server/src/simcore_service_webserver/projects/db.py
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@
ProjectNodesRepo,
)
from simcore_postgres_database.webserver_models import ProjectType, projects, users
from sqlalchemy import func, literal_column
from sqlalchemy import func, literal_column, sql
from sqlalchemy.dialects.postgresql import BOOLEAN, INTEGER
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.sql import ColumnElement, CompoundSelect, Select, and_
@@ -370,6 +370,159 @@ async def upsert_project_linked_product(
).group_by(project_to_groups.c.project_uuid)
).subquery("access_rights_subquery")

def _create_private_workspace_query(
self,
*,
product_name: ProductName,
user_id: UserID,
workspace_query: WorkspaceQuery,
project_tags_subquery: sql.Subquery,
is_search_by_multi_columns: bool,
user_groups: list[RowProxy],
) -> sql.Select | None:
private_workspace_query = None
if workspace_query.workspace_scope is not WorkspaceScope.SHARED:
assert workspace_query.workspace_scope in ( # nosec
WorkspaceScope.PRIVATE,
WorkspaceScope.ALL,
)

private_workspace_query = (
sa.select(
*PROJECT_DB_COLS,
projects.c.workbench,
self._access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
sa.func.coalesce(
project_tags_subquery.c.tags,
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.Integer)),
).label("tags"),
)
.select_from(
projects.join(self._access_rights_subquery, isouter=True)
.join(projects_to_products)
.join(
projects_to_folders,
(
(projects_to_folders.c.project_uuid == projects.c.uuid)
& (projects_to_folders.c.user_id == user_id)
),
isouter=True,
)
.join(project_tags_subquery, isouter=True)
)
.where(
(
(projects.c.prj_owner == user_id)
| sa.text(
f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_groups)})"
)
)
& (projects.c.workspace_id.is_(None)) # <-- Private workspace
& (projects_to_products.c.product_name == product_name)
)
)
if is_search_by_multi_columns:
private_workspace_query = private_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)

return private_workspace_query

def _create_shared_workspace_query(
self,
*,
product_name: ProductName,
workspace_query: WorkspaceQuery,
project_tags_subquery: sql.Subquery,
user_groups: list[RowProxy],
is_search_by_multi_columns: bool,
) -> sql.Select | None:

if workspace_query.workspace_scope is not WorkspaceScope.PRIVATE:
assert workspace_query.workspace_scope in (
WorkspaceScope.SHARED,
WorkspaceScope.ALL,
)
workspace_access_rights_subquery = (
sa.select(
workspaces_access_rights.c.workspace_id,
sa.func.jsonb_object_agg(
workspaces_access_rights.c.gid,
sa.func.jsonb_build_object(
"read",
workspaces_access_rights.c.read,
"write",
workspaces_access_rights.c.write,
"delete",
workspaces_access_rights.c.delete,
),
)
.filter(workspaces_access_rights.c.read)
.label("access_rights"),
).group_by(workspaces_access_rights.c.workspace_id)
).subquery("workspace_access_rights_subquery")

shared_workspace_query = (
sa.select(
*PROJECT_DB_COLS,
projects.c.workbench,
workspace_access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
sa.func.coalesce(
project_tags_subquery.c.tags,
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.Integer)),
).label("tags"),
)
.select_from(
projects.join(
workspace_access_rights_subquery,
projects.c.workspace_id
== workspace_access_rights_subquery.c.workspace_id,
)
.join(projects_to_products)
.join(
projects_to_folders,
(
(projects_to_folders.c.project_uuid == projects.c.uuid)
& (projects_to_folders.c.user_id.is_(None))
),
isouter=True,
)
.join(project_tags_subquery, isouter=True)
)
.where(
(
sa.text(
f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_groups)})"
)
)
& (projects_to_products.c.product_name == product_name)
)
)
if workspace_query.workspace_scope == WorkspaceScope.ALL:
shared_workspace_query = shared_workspace_query.where(
projects.c.workspace_id.is_not(None) # <-- All shared workspaces
)
else:
assert workspace_query.workspace_scope == WorkspaceScope.SHARED
shared_workspace_query = shared_workspace_query.where(
projects.c.workspace_id
== workspace_query.workspace_id # <-- Specific shared workspace
)

if is_search_by_multi_columns:
# NOTE: fields searched with text include user's email
shared_workspace_query = shared_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)

return shared_workspace_query

return None

async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-statements,too-many-branches
self,
*,
@@ -400,26 +553,6 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st

async with self.engine.acquire() as conn:
user_groups: list[RowProxy] = await self._list_user_groups(conn, user_id)

workspace_access_rights_subquery = (
sa.select(
workspaces_access_rights.c.workspace_id,
sa.func.jsonb_object_agg(
workspaces_access_rights.c.gid,
sa.func.jsonb_build_object(
"read",
workspaces_access_rights.c.read,
"write",
workspaces_access_rights.c.write,
"delete",
workspaces_access_rights.c.delete,
),
)
.filter(workspaces_access_rights.c.read)
.label("access_rights"),
).group_by(workspaces_access_rights.c.workspace_id)
).subquery("workspace_access_rights_subquery")

project_tags_subquery = (
sa.select(
projects_tags.c.project_id,
@@ -433,127 +566,25 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
# Private workspace query
###

if workspace_query.workspace_scope is not WorkspaceScope.SHARED:
assert workspace_query.workspace_scope in ( # nosec
WorkspaceScope.PRIVATE,
WorkspaceScope.ALL,
)

private_workspace_query = (
sa.select(
*PROJECT_DB_COLS,
projects.c.workbench,
self._access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
sa.func.coalesce(
project_tags_subquery.c.tags,
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.Integer)),
).label("tags"),
)
.select_from(
projects.join(self._access_rights_subquery, isouter=True)
.join(projects_to_products)
.join(
projects_to_folders,
(
(projects_to_folders.c.project_uuid == projects.c.uuid)
& (projects_to_folders.c.user_id == user_id)
),
isouter=True,
)
.join(project_tags_subquery, isouter=True)
)
.where(
(
(projects.c.prj_owner == user_id)
| sa.text(
f"jsonb_exists_any(access_rights_subquery.access_rights, {assemble_array_groups(user_groups)})"
)
)
& (projects.c.workspace_id.is_(None)) # <-- Private workspace
& (projects_to_products.c.product_name == product_name)
)
)
if search_by_multi_columns is not None:
private_workspace_query = private_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)
else:
private_workspace_query = None
private_workspace_query = self._create_private_workspace_query(
product_name=product_name,
user_id=user_id,
workspace_query=workspace_query,
project_tags_subquery=project_tags_subquery,
is_search_by_multi_columns=search_by_multi_columns is not None,
user_groups=user_groups,
)

###
# Shared workspace query
###

if workspace_query.workspace_scope is not WorkspaceScope.PRIVATE:

assert workspace_query.workspace_scope in ( # nosec
WorkspaceScope.SHARED,
WorkspaceScope.ALL,
)

shared_workspace_query = (
sa.select(
*PROJECT_DB_COLS,
projects.c.workbench,
workspace_access_rights_subquery.c.access_rights,
projects_to_products.c.product_name,
projects_to_folders.c.folder_id,
sa.func.coalesce(
project_tags_subquery.c.tags,
sa.cast(sa.text("'{}'"), sa.ARRAY(sa.Integer)),
).label("tags"),
)
.select_from(
projects.join(
workspace_access_rights_subquery,
projects.c.workspace_id
== workspace_access_rights_subquery.c.workspace_id,
)
.join(projects_to_products)
.join(
projects_to_folders,
(
(projects_to_folders.c.project_uuid == projects.c.uuid)
& (projects_to_folders.c.user_id.is_(None))
),
isouter=True,
)
.join(project_tags_subquery, isouter=True)
)
.where(
(
sa.text(
f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, {assemble_array_groups(user_groups)})"
)
)
& (projects_to_products.c.product_name == product_name)
)
)
if workspace_query.workspace_scope == WorkspaceScope.ALL:
shared_workspace_query = shared_workspace_query.where(
projects.c.workspace_id.is_not(
None
) # <-- All shared workspaces
)
else:
assert ( # nosec
workspace_query.workspace_scope == WorkspaceScope.SHARED
)
shared_workspace_query = shared_workspace_query.where(
projects.c.workspace_id
== workspace_query.workspace_id # <-- Specific shared workspace
)

if search_by_multi_columns is not None:
# NOTE: fields searched with text include user's email
shared_workspace_query = shared_workspace_query.join(
users, users.c.id == projects.c.prj_owner, isouter=True
)

else:
shared_workspace_query = None
shared_workspace_query = self._create_shared_workspace_query(
product_name=product_name,
workspace_query=workspace_query,
project_tags_subquery=project_tags_subquery,
user_groups=user_groups,
is_search_by_multi_columns=search_by_multi_columns is not None,
)

###
# Attributes Filters